Blossom module

Developer productivity Unbundled: Extension

Edition

CE

License

MLA, GPL

Issues

Maven site

Latest

4.0.0

The Blossom module makes the Spring Framework available for Magnolia. Blossom enables you to create editing components that display a high level of dynamic behavior. These components can be used by editors to create truly interactive web pages. For example, the Spring Framework application stack makes it easier to connect to business systems, so that they fetch the information that you want to present in your pages. As Blossom is built on the Spring Web MVC, familiarity with this framework will ensure a smooth experience working with the module.

Key Blossom functionality

  • Annotation based API that builds on the Spring Web MVC. The Blossom API automatically detects annotated classes and registers them for use in Magnolia. To do this, simply add @Template to your controllers and they are ready to be used as building blocks by editors.

  • Exposes controllers as templates and components, allowing you to use the controller for building a model. Useful if you need to call a web service for information that you need to present or if you need to read information from a database.

  • Template based. Having templates (pages, areas and components) backed by an MVC framework has the benefit of providing natural web development business logic.

  • Enable the re-use of previously created controllers.

  • Allows you to create dialogs with code rather than configuration. This has many benefits:

    • you can populate the dialog at runtime with options detected at runtime:

    • you can obtain dialogs from the repository and place into your source control (CVS, SVN, GIT).

  • Components can be executed in front of Magnolia. This means that it can choose to do a redirect and skip page rendering. This is very useful, for instance, if you have a form that on post should either present an error message or do a redirect.

Compatibility requirements

Magnolia version Blossom release branch Spring version

6.2

3.4

5.1.0.RELEASE

5.7

3.2

3.2.0.RELEASE

We recommend using the latest version of the Blossom module.

Node and node path have been made available to the 6 UI apps in DialogCreationContext only since version 3.4.5.

The UI 6 definitions are only "natively" supported since version 3.5.0.

For more information about updating the module and its compatibility, see:

Installing with Maven

Maven is the easiest way to install the module. Add the following to your bundle:

<dependency>
  <groupId>info.magnolia.blossom</groupId>
  <artifactId>magnolia-module-blossom</artifactId>
  <version>4.0.0</version> (1)
</dependency>
1 Should you need to specify the module version, do it using <version>.

Add the relevant Spring dependencies as well (see the Compatibility section above for versions).

The Magnolia Blossom Personalization module is also required. If you are using Magnolia 6.3 and higher, you need:
<dependency>
  <groupId>info.magnolia.personalization</groupId>
  <artifactId>magnolia-personalization-blossom-compatibility</artifactId>
  <version>3.0.2</version> (1)
</dependency>
1 Should you need to specify the module version, do it using <version>.
If you are using an older branch of Magnolia:
<dependency>
  <groupId>info.magnolia.personalization</groupId>
  <artifactId>magnolia-personalization-blossom</artifactId>
  <version>3.0.2</version> (1)
</dependency>
1 Should you need to specify the module version, do it using <version>.

Getting Started

Programming model

Blossom is an annotation based API that’s used in combination with Spring MVC. This snippet gives an overview of how the API is used.

@Template(id = "myModule:components/text", title = "Text")
@Controller
public class TextComponent {

    @TabFactory("Content")
    public void contentTab(UiConfig cfg, TabBuilder tab) {
        tab.fields(
                cfg.fields.text("heading").label("Heading"),
                cfg.fields.richText("body").label("Text body").required()
        );
    }

    @TabFactory("Margins")
    public void marginsTab(TabBuilder tab) {
    }

    @RequestMapping("/text")
    public String render() {
        return "components/text.ftl";
    }
}

@Template(id = "myModule:components/main", title = "Main")
@Controller
public class MainTemplate {

    @DialogFactory("frontpage-properties")
    public void frontPageProperties(UiConfig cfg, DialogBuilder dialog) {
        dialog.form().tabs(
                cfg.forms.tab("Properties").fields(
                        cfg.fields.text("headline").label("Headline").description("The text to use as a headline")
                )
        );
    }

    @RequestMapping("/main")
    public String render() {
        return "pages/mainTemplate.ftl";
    }

    @Area("Content")
    @AvailableComponentClasses({TextComponent.class})
    public static class ContentArea {

        @RequestMapping("/main/content")
        public String render() {
            return "areas/contentArea.ftl";
        }
    }
}

@DialogFactory("main-properties")
public class MainDialogFactory {

    @TabFactory("Properties")
    public void propertiesTab(UiConfig cfg, TabBuilder tab) {
    }
}

Running the sample project

This sample is a complete web application based on the current Magnolia and requires nothing more than a Maven installation.

Before Blossom 3.4.7, the sample was in a separate samples Git project. Now it is part of the blossom Git repository.
  1. Clone the blossom repository. If you work with an earlier version of Blossom (see Compatibility requirements), switch to the respective release branch.

  2. Build the project and run magnolia-blossom-sample-webapp using mvn jetty:run-war.

  3. When Maven has downloaded the necessary artifacts and started the application, point your browser to localhost:8080.

  4. Magnolia will now display a list of modules for install. Complete the installation wizard and log in using superuser for both the username and password.

git clone https://git.magnolia-cms.com/scm/modules/blossom.git
cd blossom
mvn clean verify
cd magnolia-blossom-sample-webapp
export MAVEN_OPTS="-XX:-CMSClassUnloadingEnabled -XX:PermSize=256M -XX:MaxPermSize=512M -Xmx512m"
mvn jetty:run-war

Creating your own project

We recommend using the archetypes to create your own project. For more details see this Getting started with Blossom.

Updating to Magnolia 6.3

Since version 3.5.0 of the Blossom module, @DialogFactories and @TabFactories can provide 6 UI definitions.

Instead of injecting the former TabBuilder, you can add your FieldDefinitions directly to a List<EditorPropertyDefinition>, which the @TabFactories may consume, or now also return.

Example
@TabFactory("Content")
public List<EditorPropertyDefinition> contentTab() {
    TextFieldDefinition heading = new TextFieldDefinition();
    heading.setName("heading");
    RichTextFieldDefinition body = new RichTextFieldDefinition();
    body.setName("body");
    body.setRequired(true);

    return List.of(heading, body);
}

5 UI (compatibility) definitions

It is also possible to keep the existing dialog factories, using 5 UI compatibility definitions, through UiConfig, TabBuilder and so on. In this case, the definitions are converted on the fly to the equivalent 6 UI dialogs. The conversion is done using the Field converters.

  • Custom fields require an extra field converter, along with the updated Vaadin 8 field implementation. These may be registered into the DefinitionConverter#FIELD_DEFINITION_CONVERTERS within a static initializer block.

  • The DialogCreationContext#getItem() is deprecated without replacement.

  • The custom formDialogPresenterClass is no longer supported in 6 UI apps.

While the Magnolia 6 UI framework does not have configurable form-dialog presenters, nor Vaadin 7 items, the 5 UI (compatibility) Pages app may still be used for cases relying on them.

Definition builders

The 6 UI framework no longer provides definition builders. Use plain definitions via constructors and setters.

The following third-party library provides builders for 6 UI definitions:

What’s new in Blossom 3

Major update for Magnolia 5 style dialogs, compatible with Magnolia 5.1 and later. In version 5, Magnolia has a brand new user interface and with it comes a new API for dialogs. This version of Blossom is an update for this API and also contains a few other improvements.

Fluent builder style API for dialogs

Building dialogs is now done using a fluent builder style API with a very compact syntax. The builders allow for controlling every detail of how a field should appear and behave. Where in the old API you had to resort to configure the controls using key and value pairs the new API has chainable setters for every property.

Meta-annotations for component availability

Components can be categorized using custom meta-annotations, these can then be specified when setting the available components on areas. All components having this component category will be available in the area.

Support for autowiring in RenderingModels

AbstractAutowiredRenderingModel is an abstract class for RenderingModels that need be to be autowired using Spring.

Support for Spring MVC 3.1 MVC handler methods

In Spring MVC 3.1 the implementation of annotated MVC was reimplemented adding new handler adapters and handler mappings, starting with Blossom 3.0.2 handlers detected using the new implementation are discovered.

Post processing callbacks prior to registration of templates, areas, components and dialogs

Blossom 3.0.2 introduced a new annotation @PreRegister to be used on methods that should be called prior to registration with Magnolia.

Auto generation of components in areas

The process of auto generation gives areas the opportunity to pre-populate areas with components to save editors some work.

What’s new in Blossom 2

Major update for Magnolia 4.5 and its new rendering engine, page editor and templating concepts. The term paragraph is replaced with component. Components and templates have been streamlined and are internally identical, the only real difference is that they have different id formats which in turn controls where they’re applicable.

New id conventions for templates

In Magnolia 4.5 template IDs are in the format of <moduleName>:<path>. (See the Templating documentation.) As a consequence the Blossom convention was updated. You now need to specify the ID explicitly on your controller: @Template(id="myModule:pages/news", title="News").

For help on how to migrate your templates to the new naming convention, see Migrating content when upgrading to Blossom 2.

Areas

With Magnolia 4.5 areas are explicit entities rather than implicit as node collections. They’re declared as nested classes (public static) directly within the page templates they belong to.

Reference

This is the reference documentation for Blossom. The sample code in this section uses Spring 2.5+ annotated controllers.

Templates

To make a controller function as a template, simply add the @Template annotation. For example:

@Template(id = "myModule:pages/section", title = "Section Template")
@Controller
public class SectionController {

    @RequestMapping("/section")
    public ModelAndView render() {
        ...
    }
}

This controller will be exposed in Magnolia, showing up as Section Template. Note that the id follows a convention used in Magnolia: module name, followed by a path. For templates that you want to use for pages, the path needs to start with pages.

Controlling where a template can be used

Where a page template is available for editors to use can be customized in two ways. Either using the site module and configure in the site definition or by using the @Available annotation. If the @Available is used it will override any setting in the site definition. Use it on a method in your controller that returns boolean. It can receive as arguments an argument of type Node or Content, this is the page in the website for which to determine if the template is available. It can also accept an argument of type TemplateDefinition this is the internal definition used by Magnolia:

    @Available
    public boolean isAvailable(Node websiteNode) {
        // Replace this with logic for your specific use case
        return true;
    }

For more details see the documentation on the site definition.

Areas

Areas are defined using nested classes (public static class) within a template or within an other area. Areas are standard Spring Web MVC controllers annotated with @Area. In addition Areas have their own dialog.

@Template(title = "Main", id = "myModule:pages/main")
@Controller
public class MainTemplate {

    @Area("Promos")
    @Inherits
    @AvailableComponentClasses({TextComponent.class})
    @Controller
    public static class PromosArea {

        @RequestMapping("/mainTemplate/promos")
        public String render() {
            return "areas/promos.ftl";
        }
    }

    // rest of the template excluded for brevity
}

Controlling which components can be added to an area

To specify which components can be used in an area you can either list the ids of the components using the @AvailableComponents annotation, list their classes using the @AvailableComponentClasses annotation, or use meta-annotations to categorise your components. It’s possible to use all three options on the same area. Meta-annotations are annotations that you create yourself and use on your components. They need to annotated with @ComponentCategory.

This annotation is a component category annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ComponentCategory
public @interface Promo {
}

Any controller annotated with it will be included in an area that specifies it.

@Controller
@Template(title = "Text", id = "myModule:components/text")
@Promo
public class TextComponent {
    ...
}

Area inheritance

A common requirement is to have every page on a site or a whole section to have a consistent base look, sharing header, footer and banners. Area inheritance is a feature that makes an area include components from its parent pages. To use this feature use the @Inherited annotation on an area.

@Inherits

By default the annotation is set to filtered mode, where it will only include components explicitly marked to be inherited. This is done using a property named inheritable. To flag it the component needs to have a checkbox added to its dialog.

cfg.fields.checkbox("inheritable").label("Inherited")

Filtering mode can be turned off causing all components to be inherited.

@Inherits(components = ComponentInheritanceMode.ALL)

The annotation also allows you to control how properties are inherited. These are properties on the area itself. A property set on the area in the parent page is then available when the area is rendered in the child page.

@Inherits(properties = PropertyInheritanceMode.ALL)

Maximum number of components in area

Sometimes its helpful to limit the number of components an editor can add into an area. On the area annotation you can specify the limit your design requires.

@Area(name = "Promos", maxComponents = 5)

Reusing areas between templates through class inheritance

It’s common that many templates in a project have the same set of areas by taking advantage of the class hierarchy you only need to define them once.

public abstract class AbstractTemplate {

    @Controller
    @Area("headerArea")
    public static class HeaderArea {

        @RequestMapping("/headerArea")
        public String render() {
            return "areas/headerArea";
        }
    }
}

@Controller
@Template(...)
public class ExampleTemplate extends AbstractTemplate {
}

Auto generation

To reduce the effort needed by editors to create pages it can be useful to pre-populate pages by automatically adding (generating) components in areas. To use this feature add a method on your area and annotate it with @AutoGenerator. It will receive the Node for the area content.

@AutoGenerator
public void generate(Node node) {
    // if node has no sub nodes add nodes for components
}

Components

Components are controllers annotated with @Template and having an id in the format moduleName:components/*.

@Template(id = "myModule:components/textAndImage", title="Text and Image")
@TemplateDescription("Adds a text section with an image")
@Controller
public class TextAndImageController {

    @RequestMapping("/textAndImage")
    public ModelAndView render() {
        ...
    }
}

This controller will be visible as the Text and Image component. The description set using @TemplateDescription is shown when you choose a component to add in the page editor.

Accessing content in controllers

In controllers you can have the content object passed directly to your method. In templates this is the content object for the page, in components it’s the content object for the component within the page. If you declare two arguments of type Content or Node the first one will be the content of the page and the second will be the content of the component. Other arguments supported are AggregationState, Context, WebContext, User and MgnlUser:

@RequestMapping("/book")
public String render(Node pageNode, Node componentNode) {
    ...
}

To do this, configure the BlossomHandlerMethodArgumentResolver on your RequestMappingHandlerAdapter.

Accessing Magnolia context objects in controllers

In addition to the content you can access a range of objects about the current rendering context by adding them as arguments to your @RequestMapping annotated methods.

  • info.magnolia.rendering.template.TemplateDefinition The definition of the template as generated by Blossom based on the controllers annotations.

  • info.magnolia.rendering.template.AreaDefinition When rendering an area this is the definition of the area generated by Blossom.

  • info.magnolia.cms.core.AggregationState Aggregation of state about an operation in the CMS

  • info.magnolia.context.Context Context regarding an operation in the CMS and methods for accessing the JCR.

  • info.magnolia.context.WebContext Specialization of Context for web requests.

  • info.magnolia.cms.security.User The current user.

  • info.magnolia.cms.security.MgnlUser The current user as a instance of the default User implementation.

  • info.magnolia.cms.core.Channel The current channel being rendered for in a multi-channel setup.

  • info.magnolia.module.site.theme.Theme The current theme.

  • info.magnolia.module.site.Site The current site.

Template parameters

On templates, areas and components you can declare parameters that can then be accessed in the view using the def.parameters.<parameter-name> notation.

Example:

@Controller
@Template(title = "Main", id = "blossomSampleModule:pages/main")
@TemplateParams({
        @TemplateParam(name = "example", value="my-value")
})

To access the example parameter in a freemarker view use:

${def.parameters.example!}

View rendering

View rendering with Blossom is performed by Spring. Blossom comes with support for using the Magnolia rendering chain. As a result you’ll have access to all the objects that Magnolia provides to template scripts.

Content redirects

Blossom also provides a convenient way of returning redirects to content. By adding the UuidRedirectViewResolver you can return the uuid to redirect to directly from your controller. This example returns a redirect to a page:

    return "website:" + content.getUUID();

This example returns a redirect for a resource in the documents repository:

    return "dms:" + content.getUUID();

Redirecting to the page currently being rendered is a very common case and Blossom has a shortcut syntax that does exactly this without requiring the uuid.

return "magnolia-redirect:main-content";

It’s also possible to redirect to the current content being rendered when executing further down the content hierarchy.

return "magnolia-redirect:current-content";

Thymeleaf views

If you prefer using Thymeleaf there is a project on GitHub by Thomas Kratz that adds support for it to Blossom. It is fully functional and is in production use.

Substituting Blossom view resolvers with custom resolvers

If you substitute or combine the Blossom view resolvers with your own implementations, be aware that the GZip filter included in Magnolia wraps the response object and gives it some non-standard semantics. It will tell you that the response is not committed even though it has been written to. It will also allow you to do redirects even though the response has been written to.

Be aware that if you write controllers that exploit the GZip filter, you are creating a dependency and you will need to have the GZip filter active. Certain frameworks such Spring and WebFlow will test if the response is committed to detect if redirects are possible and sometimes to choose between doing a forward or an include.

Dialogs

Blossom allows you to create dialogs using code. One of the benefits of this is that when the controller fetches data from a web service you can populate your dialog with options retrieved from that web service. See also Dialogs.

For example, if the controller displays a list of the best selling books in a category of books you could call the web service to find out what categories are available and use them to populate a select control. Another benefit is that you get your dialogs out of the repository and into version control. You get to version your dialogs together with your controllers.

You can easily change your dialog code in your IDE, compile and hot swap the new code into an instance of Magnolia running in debug mode. This makes developing dialogs extremely fast.

Fluent builder style API

Dialogs are described using a model of definition classes. These define the structure of the dialog, its tabs and its fields. They describe both what the dialogs and its forms should look like and how they should behave. Blossom uses a fluent builder style API to construct these definitions. The API consists of config classes that produce builders, the builders produce definitions.

The central config class is UiConfig which is used to create tabs and all different kinds of fields provided by Magnolia.

Blossom provides UiConfig to all methods that create dialogs or populate dialogs. In addition it will also provide a TabBuilder or DialogBuilder, depending on the use case. These represent the tab or dialog that’s being populated.

Note that is also possible to extend the dialog creation mechanism in Blossom to use custom config classes or replace UiConfig or TabBuilder with classes of your own for adding your custom or third-party fields. The article Extending the Blossom TabBuilder on the wiki explains how this is done in detail.

Dialog factories

Dialog factories create dialogs. Although the dialogs are automatically exposed in Magnolia, you need to configure them as beans in Spring. The easiest way to do this is to use Spring 2.5+ component scan, however manual configuration in XML will work. For example:

@DialogFactory("front-page-dialog")
@TabOrder({"Content", "Margins"})
public class FrontPageDialog {

    @TabFactory("Margins")
    public void marginsTab(UiConfig cfg, TabBuilder tab) {
        tab.fields(
                cfg.fields.staticField("static").description("Margins around the side of the front page"),
                cfg.fields.text("leftMargin").label("Left Margin").description("Left margin in pixels"),
                cfg.fields.text("rightMargin").label("Right Margin").description("Right margin in pixels");
    }

    @TabFactory("Content")
    public void contentTab(UiConfig cfg, TabBuilder tab) {
        tab.fields(
                cfg.fields.text("title").label("Title").description("The title of this page")
        );
    }
}

This registers a dialog in Magnolia with the name front-page-dialog. It has two tabs, Content and Margins. Methods annotated with @TabFactory in superclasses will be called. If there are many dialogs that look the same you can create an abstract base class for them.

The order that the dialogs appear in has been set using the @TabOrder annotation. It sorts them based on their names. If you would rather sort them by their names you can change it with a property in your module descriptor.

<properties>
  <property>
    <name>magnolia.blossom.sortTabsByLabel</name>
    <value>false</value>
  </property>
</properties>

Templates create their own dialog

A template always has a dialog so you don’t have to create a dialog factory and link them with a name. Instead your controller will act as its own dialog factory. You can use the same annotations inside your controller that you can for dialog factories. (See above.) If you want your template to use a dialog that is created by a dialog factory or configured in the repository, it’s possible to override this behavior and specify which dialog should be used:

@Template(dialog="my-dialog")

Templates can contain dialog factories

Because templates are often designed for use with few dialogs, Blossom allows you to create these within your template class for ease of location. The concept is similar to dialog factories but here everything is done in a single method.

@Controller
@Template(id = "myModule:pages/productTemplate", title = "Product Template")
public class ProductTemplate {

    @DialogFactory("product-dialog")
    public void productDialog(UiConfig cfg, DialogBuilder dialog) {
        dialog.form().tabs(
                cfg.forms.tab("Settings").fields(
                        cfg.fields.text("title").label("Title")
                ),
                cfg.forms.tab("Properties").fields(
                        cfg.fields.text("headline").label("Headline")
                )
        );
    }
}

Dialogs and the class hierarchy

It’s not uncommon to have several dialogs that are very similar. For instance you might want to have the same basic sets of tabs in a number of dialogs. Blossom makes this possible by allowing you to define tab factories in super classes. Methods are invoked in superclass-first order, methods within the same class have no defined order of invocation.

For instance a tab for meta data in an abstract base class extended by several templates.

public abstract class BasePageTemplate {

    @TabFactory("Meta")

    public void metaTab(UiConfig cfg, TabBuilder tab) {

        tab.fields(
            cfg.fields.text("metaAuthor").label("Author"),
 cfg.fields.text("metaKeywords").label("Keywords"),

            cfg.fields.text("metaDescription").label("Description")
        );
    }

}

Post create callbacks for dialogs

Tab factories will always add a tab and then call your method to have it populated. If you wish to only add the tab in certain scenarios you can instead use a post create callback. You can then determine the scenario, for instance testing if the user has a certain role or if the page is in a certain part of the website tree. They can also be useful when you need to do changes to the dialog after all tab factories have been added. Methods are invoked in superclass-first order and always after tab factories in the same class, post create methods within the same class have no defined order of invocation.

In this example a field is added if a criteria is met.

@PostCreate
public void postCreate(DialogBuilder dialog, UiConfig cfg) {
    if (some criteria) {
        dialog.form().tab("Content").label("Content").fields(
                cfg.fields.text("header").label("header")
        );
    }
}

Custom dialog actions

Blossom will add the standard save and cancel actions to the dialog before it is shown to the user unless any have been configured.

This example shows how to configure a custom save action using a post create callback.

@PostCreate
public void postCreate(UiConfig cfg, DialogBuilder dialog) {
    // Add custom save action
    MyCustomSaveDialogActionDefinition save = new MyCustomSaveDialogActionDefinition();
    save.setName("commit");
    save.setLabel("save");
    dialog.addAction(save);

    // Add the standard cancel action
    CancelDialogActionDefinition cancel = new CancelDialogActionDefinition();
    cancel.setName("cancel");
    cancel.setLabel("cancel");
    dialog.addAction(cancel);
}

Validating dialog input

To ensure that the content is correctly structured you can add validators to the fields. In the example below the name field can not be empty and the email has to be valid.

@TabFactory
public void contentTab(UiConfig cfg, TabBuilder tab) {
    tab.fields(
       cfg.fields.text("name").label("Name").required(),
       cfg.fields.text("email").label("Email").validator(cfg.validators.email())
    );
}

Using DAM fields

The DAM module has a config class of its own, DamConfig, Blossom also provides this automatically when added as an argument.

@TabFactory("Content")
public void contentTab(UiConfig cfg, DamConfig dam, TabBuilder tab) {
    tab.fields(
            cfg.fields.text("title").label("Title"),
            cfg.fields.richText("body").label("Text"),
            dam.fields.assetLink("photo").label("Photo")
    );
}

Dialog and tab-factory parameters

Since version 3.6.0, @DialogFactories and @TabFactories can inject Spring-managed beans, as well as parameters resolved through Spring HandlerMethodArgumentResolvers (including custom resolvers).

@TabFactory("Content")
public List<EditorPropertyDefinition> contentTab(HelloService helloService) {
    TextFieldDefinition heading = new TextFieldDefinition();
    heading.setName("heading");
    heading.setDefaultValue(helloService.sayHello());
    return List.of(heading, body);
}

Post processing callbacks prior to registration of templates, areas, components and dialogs

Before Blossom registers templates, areas, components and dialogs with Magnolia at startup, effectively making them available for use, it will inspect its class for methods annotated with @PreRegister and invoke those methods. This can be used to customize the definition Blossom built based on the annotations present on the class.

@PreRegister in template or component

@PreRegister
public void register(BlossomTemplateDefinition templateDefinition) {
}

@PreRegister in area

@PreRegister
public void register(BlossomAreaDefinition areaDefinition) {
}

@PreRegister in dialog

@PreRegister
public void register(BlossomDialogDescription dialogDescription) {
}

Pre-execution of components

Pre-execution allows you to write components that can take care of the entire page rendering. This is necessary if you want to do redirects. Another scenario is if you want to have your controller rendering a form into the page on GET requests and write an XML document or a PDF on POST requests.

Note that this feature is enabled by default from version 1.1 and disabled by default in 0.5. To enable it you need to change a flag in the repository. Navigate to config:/server/filters/cms/blossom in AdminCentral and change the property enabled from `false to'`true`’.

In order for pre-execution to work you need to use the BlossomHandlerMapping. See section on this page on Handler Mapping.

Pre-execution is performed by a filter that will intercept requests and look for a request parameter that contains the UUID of the component that is to be pre-executed. The request parameter is named _pecid. Blossom provides a taglib to make it easier to include this request parameter.

The blossom taglib has two tags, <blossom:pecid-input /> that will output <input type="hidden" name="_pecid" value="<component uuid>" />. This tag is also available under the alias <blossom:pecidInput />, this is necessary for use in Freemarker templates.

<%@ taglib uri="blossom-taglib" prefix="blossom" %>
<form action="?" method="POST">
    <blossom:pecid-input />
    <input type="text" name="q" />
    <input type="submit" value="Search" />
</form>

It is possible to use <blossom:pecid var="pecid" /> to set the UUID of the component as a variable, in this example named pecid. The var attribute is optional. If it’s not specified the component UUID is written directly into the page.

<a href="/news/new-website-launched.html?_pecid=<blossom:pecid />"

When you have multiple forms on the same page it can be helpful to distinguish which one is supposed to handle a POST request by testing in a controller if it is being pre-executed.

if (request.getMethod().equals("POST") && PreexecutionUtils.isPreexecuting() {
    processFormSubmit(request);
} else {
    showForm(request);
}

At the end of this article you’ll find a full example of how to use pre-execution.

Handler Mapping

Spring uses HandlerMappings to map a request to a handler (usually a controller). Blossom interrogates the HandlerMappings to find out how you have mapped your handlers. Your HandlerMapping needs to be a class inherited from AbstractUrlHandlerMapping. This is usually the case, both Spring 2.5+ annotated controllers and BeanNameUrlHandlerMapping inherit from AbstractUrlHandlerMapping.

If you want support for pre-execution (see below) you will need to have BlossomHandlerMapping delegate to your HandlerMapping. This is necessary because this is where pre-execution is taken care of.

Note, you have to explicitly declare your HandlerMapping in XML. Although Spring will supply you with defaults even if you do not use XML, it is not possible for Blossom ot get a reference to the defaults and therefore will not take part in the rendering.

Localization

Using @I18nBasename you can set the name of the resource bundle you want to use. This can be used on templates, areas and components and in dialog factories. Labels and descriptions set for instance using the @Template and @TabFactory annotations will then be localized using the specified resource bundle.

@Controller
@Template(title = "Text", id = "blossomSampleModule:components/text")
@I18nBasename("info.magnolia.blossom.sample.messages")
public class TextComponent {

  @TabFactory("textComponent.contentTab.label")
  public void contentTab(UiConfig cfg, TabBuilder tab) {
    tab.fields(
      cfg.fields.text("heading").label("textComponent.contentTab.heading")
    );
  }
}

You can also leave labels and titles empty to have Magnolia resolve them automatically by generating a key based on the dialog’s name, the tab’s name, and the field’s name. However by default Blossom will use the value given in @TabFactory both for name and label of the tab. In order to leave the label empty so automatic i18n takes effect you need to set a property in your module descriptor.

  <properties>
    <property>
      <name>magnolia.blossom.setTabLabels</name>
      <value>false</value>
    </property>
  </properties>

Spring i18n interoperability

Blossom provides you with a LocaleResolver to enable Spring to pick up the locale that Magnolia has selected for the current request:

info.magnolia.module.blossom.context.MagnoliaLocaleResolver

To bridge Magnolia i18n into Spring, use the following MessageSource implementation:

info.magnolia.module.blossom.context.MagnoliaMessageSource

Content translation

If the content of a tab field should be translatable then it can be made i18n aware by using the i18n() method.

 @TabFactory("textComponent.contentTab.label")
  public void contentTab(UiConfig cfg, TabBuilder tab) {
    tab.fields(
      cfg.fields.text("heading").label("textComponent.contentTab.heading").i18n()
    );
  }

Sites and the template prototype

In the site definition you can define a template prototype which is a template that other templates inherit from. Things configured on the prototype such as areas are copied onto other templates.

To use this feature with Blossom you need to use a special view renderer. Blossom provides a FactoryBean for creating this renderer, SiteAwareFreemarkerTemplateViewRendererFactoryBean.

The Blossom sample has example configuration for this renderer and the getting started guide uses a Maven archetype that generates this configuration for you.

For more details see the documentation on the site definition.

Multipart Requests

Because Magnolia handles multipart requests, Blossom provides a MultipartResolver implementation. Add this snippet to your beans XML. In the example above this would be blossom-servlet.xml.

  <bean id="multipartResolver" class="info.magnolia.module.blossom.multipart.BlossomMultipartResolver" />

Virtual URI Mappings

Any bean implementing VirtualUriMapping that is configured with Spring is automatically detected and exposed in Magnolia. This means that you don not need to configure them in the repository as you normally would.

In addition, Blossom supports writing virtual URI mappings by annotating a class with @VirtualURIMapper. Instances of this class, when configured as a bean in Spring, will be scanned for methods that can be used for URI mapping. Any method that return String or MappingResult and accepts as arguments a String that is the incoming URI or a HttpServletRequest is used.

The returned string from a Virtual URI mapping is the new URI that Magnolia will use to look up the page in the repository that is to be rendered. The returned URI can also be prefixed with redirect:, permanent:'' or ``forward: to trigger either a temporary redirect, a permanent redirect or a forward respectively. For redirects the URI can be absolute or relative within the web application (the context path is added automatically).

@VirtualURIMapper
public class SampleURIMapper {

    public String about(String uri, HttpServletRequest request) {
        if (uri.equals("/about"))
            return "/sections/about";
        return null;
    }

    public String news(String uri, HttpServletRequest request) {
        if (uri.equals("/news"))
            return "forward:/dispatcher/news.do";
        return null;
    }
}

For more details the reference documentation on Virtual URI mapping.

Dependencies on Magnolia components

Magnolia is not based on Spring, but rather uses a container internally that manages components. It is possible to pull these components in as beans in the ApplicationContext. Here they are made available for declaring dependencies on them and as candidates for autowiring. This is an example that exposes the ModuleManager as a bean with id `moduleManager':

<blossom:component id="moduleManager" type="info.magnolia.module.ModuleManager" />

(Note the id is optional). For more details on Magnolia Components see the documentation on Configuration.

Configuring beans in the repository

Besides for storing content for web pages, the JCR repository is also widely used for configuration. One benefit of this is that configuration is easy to change while the application is running.

To read this configuration Magnolia uses a mechanism called Node2Bean to transform the configuration into plain Java beans. The process is based on reflection. Blossom provides an extension to this mechanism which, during initialization of the bean, applies post processing on it using the Spring ApplicationContext.

This makes it possible to have dependencies in the bean that should be autowired by the ApplicationContext. Note that the bean will also receive all the standard life cycle callbacks that a bean managed by Spring can use. These include:

  • InitializingBean,

  • ServletContextAware,

  • ApplicationContextAware

  • and many more.
    It can also use @PostConstruct and it will be subject for having AOP aspects applied.

The Node2Bean mechanism by default calls a method with the signature public void init() on the beans it creates if it exists. The extension provided by Blossom does not support this. Instead use @PostConstruct.

With Blossom it is possible to use this mechanism directly in Spring beans xml files. The bean is proxied and is transparently replaced if changes are made in the repository.

This is an example for a book store web site that has a discount service configured in the repository:

<blossom:configured-bean id="discountService" path="/modules/book-store-module/beans/discountService" />

This example is identical to the one above except that the configuration is observed for any changes being made. When it changes the bean is reloaded making it easy to change the discounts while the system is running:

<blossom:observed-bean id="discountService" path="/modules/book-store-module/beans/discountService" />

By default the proxy is created using JDK dynamic proxies, this proxies on an interface level. If you require proxying on the class you can specify proxy-target-class="true" and the proxy will instead be a cglib class proxy. This is analogous to the way Spring AOP uses proxies.

For more details on Node2Bean see the documentation on Configuration.

Autowiring RenderingModel classes

When using standard Magnolia templates with RenderingModel classes do their backing logic you can get access to beans managed by Spring through autowiring. Blossom provides an abstract base class for this called AbstractAutowiredRenderingModel. It will do autowiring on itself using the root web application context. Note that Magnolia will still create the RenderingModel and since it supports the @Inject annotation you should use @Autowired for the dependencies you want to from Spring.

public class MyRenderingModel extends AbstractAutowiredRenderingModel<TemplateDefinition> {

    @Autowired
    private MyService service;

    public MyRenderingModel(ServletContext servletContext, Node content, TemplateDefinition definition, RenderingModel<?> parent) {
        super(content, definition, parent, servletContext);
    }

  ...
}

In order to use this you will need to have annotation config enabled in your root web application context. Add the following to your applicationContext.xml.

<context:annotation-config/>

Interactions with <mvc:annotation-driven/>

The <mvc:annotation-driven/> tag is essentially a macro. It adds a number of beans, such as message converters, handler adapters and so on. They’re all configured with reasonable defaults that suits the majority of users. That is, until you actually need something that differs from the defaults. Then you need to remove it and explicitly configure instead.

In order for Blossom to work it customizes some of the same beans that <mvc:annotation-driven/> adds. Therefor some things will break when you add <mvc:annotation-driven/>. One thing that does break is pre-execution. In order for it to work all handler adapters need to be configured within a BlossomHandlerMapping bean.

A common reason for adding <mvc:annotation-driven/> is bean validation of submitted forms since it adds configuration for this by default. When using Blossom you need to set up configuration for bean validation explicitly. If you’ve used the archetypes to create your module this was done for you. Otherwise refer to this snippet that shows how its done:

  <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="customArgumentResolver">
      <bean class="info.magnolia.module.blossom.web.BlossomWebArgumentResolver" />
    </property>
    <property name="webBindingInitializer">
      <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
        <property name="validator">
          <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
        </property>
      </bean>
    </property>
  </bean>

Additional DispatcherServlets and AJAX calls

Controllers used for rendering content are not accessible for requests coming in to the servlet container. Requests that come in are handled my Magnolia and mapped to content that is only later rendered by these controllers. The BlossomDispatcherServlet that manages these controllers are started in your module class and the servlet container has no knowledge of this servlet. Since these controllers are non meaningful without content there’s no reason to have them directly accessible.

Controllers that should be accessible directly and are not used to render content need to be managed by a different DispatcherServlet.

There’s two ways to add a DispatcherServlet in a Magnolia project. You either simply add one to web.xml or you add to your module descriptor.

Adding it to the module descriptor is the better choice because then its started after your module has started and it will become a child of the root web application context and can access all beans within it. You also won’t need to update web.xml which makes module install easier and upgrades of Magnolia easier.

The module descriptor is your /src/main/resources/META-INF/magnolia/<module name>.xml file.

Here’s an example of what that looks like:

<servlets>
  <servlet>
    <name>dispatcher</name>
    <class>org.springframework.web.servlet.DispatcherServlet</class>
    <mappings>
      <mapping>/ajax/*</mapping>
    </mappings>
    <params>
      <param>
        <name>contextConfigLocation</name>
        <value>classpath:/ajax-servlet.xml</value>
      </param>
    </params>
  </servlet>
</servlets>

Magnolia installs the servlet when the module is installed so after you’ve added it you need to reinstall your module. When it’s installed you can see it in the configuration app at /server/filters/servlets/dispatcher.

In the example above the DispatcherServlet will get all requests with a path starting with /ajax/ and it uses a configuration file/src/main/resources/ajax-servlet.xml.

You’ll want to make sure that ajax-servlet.xml has only the controllers that should be accessible from the outside and not those used for rendering content. Do this by organizing them in different packages and use component scan to find the right ones.

Here’s what a simple ajax-servlet.xml would look like:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <context:annotation-config/>

  <context:component-scan base-package="my.module.ajax" />

</beans>

It will component scan and find the controller below which becomes accessible at /ajax/hello, note that the /ajax part is not specified in the controller.

package my.module.ajax;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class AjaxController {

    @ResponseBody
    @RequestMapping("/hello")
    public String render() {
        return "Hello!";
    }
}

Examples

Using pre-execution

This is a simple controller that displays a form:

@Controller
@RequestMapping("/search")
@Template(id = "myModule:components/searchForm", title = "Search form")
@TemplateDescription("Adds a customizable search form")
public class SearchController {

    @RequestMapping(method = RequestMethod.GET)
    public ModelAndView form() {
        return new ModelAndView("searchForm");
    }

    @RequestMapping(method = RequestMethod.POST)
    public ModelAndView search(@RequestParam("q") String q, Node content) throws RepositoryException {
        if (StringUtils.isBlank(q)) {
            return new ModelAndView("searchForm", "errorMessage", content.getProperty("errorMessage").getString());
        }
        String searchUrl = content.getProperty("searchUrl").getString();
        return new ModelAndView(new RedirectView(searchUrl + q));
    }

    @TabFactory("Settings")
    public void settingsTab(UiConfig cfg, TabBuilder tab) {
        tab.fields(
                cfg.fields.text("title").label("Title").required().requiredErrorMessage("You need to enter a title!"),
                cfg.fields.text("bodyText").label("Text"),
                cfg.fields.text("searchUrl").label("Search engine URL").description("For instance: http://www.google.com/search?q="),
                cfg.fields.text("errorMessage").label("Error message").description("For instance: You need to enter a query")
        );
    }
}

The template for this component uses the blossom taglib to add the request parameter.

[#assign blossom=JspTaglibs["blossom-taglib"] /]
<h1>${content.title}/<h2>
<p>${content.bodyText}</p>
[#if errorMessage?has_content]
 <p style="color:read";">${errorMessage}</p>
[#/if]
<form action="?" method="POST">
 [@blossom.pecidInput /]
 <input type="text" name="q" />
 <input type="submit" value="Search" />
</form>

Using Spring WebFlow inside Magnolia

It is possible to include a webflow as a component and embed it into a page. The example below is a controller that exposes a booking flow:

@Controller
@Template(id="myModule:components/bookingFlow", title = "Booking Flow")
public class BookingFlowController extends FlowController {

    @Override
    @Autowired
    public void setFlowExecutor(FlowExecutor flowExecutor) {
        super.setFlowExecutor(flowExecutor);
    }

    @Override
    @RequestMapping("/booking")
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return super.handleRequest(request, response);
    }
}

Note that you need to turn off the redirectOnPause behavior since it’s not usable when the flow is embedded in a page. Also, you might want to customize the handling of NoSuchFlowException.

Feedback

DX Core

×

Location

This widget lets you know where you are on the docs site.

You are currently perusing through the DX Core docs.

Main doc sections

DX Core Headless PaaS Legacy Cloud Incubator modules