Blossom
This document applies to Blossom 2.0. If you are working with an earlier version, see Blossom 1.2 documentation.
Magnolia Blossom is the Spring integration module for Magnolia CMS. It builds on Spring Web MVC and makes it possible to create controllers that serves as templates for pages and reusable components that snaps onto any page. It lets you write components with a high level of dynamic behavior that editors can compose to create truly interactive web pages. Using the proven application stack of Spring Framework makes it easier to develop integrations with for instance business systems to fetch information you want to present in your pages. If you already know Spring you'll appreciate that Blossom gets you faster up to speed and lets you work in an environment that you're already familiar with.
Key functionality
- Annotation based API that builds on Spring Web MVC, it automatically detects annotated classes and registers them for use in Magnolia. Simply add
@Templateto your controllers and they're 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 to get information to present or if you need to read it from a database.
- Having your templates, areas and components backed by an MVC framework has the benefit of a natural way of developing business logic also for the web.
- Allows you to reuse controllers that you've already developed when you take the leap onto using a CMS.
- Allows you to create dialogs with code instead of configuration. This has many benefits: you can populate the dialog at runtime with options detected at runtime, you get your dialogs out of the repository and into your source control (CVS, SVN, GIT).
- Dialog validation callbacks for custom validation of input.
- Execution of a components in front of Magnolia CMS so that it can choose to do a redirect and skip page rendering. Very useful for instance if you have a form that on post should either present an error message or do a redirect.
- Download
- Installing
- Uninstalling
- Programming model
- Getting Started
- Creating your module
- Configuration
- Reference
- Templates
- Areas
- Components
- Dialogs
- Dialog factories
- Templates create their own dialog
- Templates can contain dialog factories
- Validating dialog input
- Customizing how a dialog is saved
- When the TabBuilder isn't enough
- Accessing content in controllers
- View rendering
- Pre-execution of components
- Handler Mapping
- Localization
- Multipart Requests
- Virtual URI Mappings
- Dependencies on Magnolia Components
- Configuring beans in the repository
- Versions and compatibility
- Resources
- Using Spring WebFlow inside Magnolia
- Examples
- Release Notes
Download
Download the Blossom module from Magnolia Store or Magnolia Maven Repository.
Installing
Blossom is a community module (4.1 and higher). To install see the general module installation instructions.
Maven users can find the artifact in the magnolia maven repository. For instructions see Maven setup on the wiki.
<dependency>
<groupId>info.magnolia</groupId>
<artifactId>magnolia-module-blossom</artifactId>
<version>2.0</version>
</dependency>
Uninstalling
See also the general module uninstalling instructions and advice.
Programming model
This is a brief example of how it's used, these classes are automatically exposed in Magnolia@Template(id = "myModule:components/text", title = "Text") @Controller public class TextComponent {@TabFactory("Content") public void addPropertiesTab(TabBuilder tab) { tab.addFckEditor("text", "Text", "").setRequired(true); }
@TabFactory("Margins") public void addMarginTab(TabBuilder tab) { }
@TabValidator("Margins") public void validateMargins() { }
@RequestMapping("/text") public String handleRequest() { return "text.jsp"; } }
@Template(id = "myModule:components/main", title = "Main") @Controller public class MainTemplate {
@DialogFactory("frontpage-properties") public void addDialog(DialogBuilder dialog) { TabBuilder tab = dialog.addTab("Properties"); tab.addEdit("headline", "Headline", "The text to use as a headline"); }
@RequestMapping("/main") public String handleRequest() { return "mainTemplate.ftl"; }
@Area("Content") @AvailableComponentClasses(TextComponent.class) public static class ContentArea {
@RequestMapping("/main/content") public String handleRequest() { return "contentArea.jsp"; } } }
@DialogFactory("main-properties") public class MainDialogFactory {
@TabFactory("Properties") public void addPropertiesTab(TabBuilder tab) { }
@DialogValidator public void validate(Dialog dialog) { } }
Getting Started
The fastest way to get a feel for the programming model and how the module works is to run the sample. It's a complete web application based on Magnolia 4.5 and requires nothing more than a maven installation.
Checkout or export the sources and run using mvn jetty:run-war. When maven has downloaded all the artifacts necessary and started the application point your browser to localhost:8080. Magnolia will be in install mode and display a list of modules it will install. Complete the installation wizard and then login using superuser for both username and password.
Creating your module
Have your module class extend from BlossomModuleSupport and implement your start and stop methods to look something like this:
public class BlossomSampleModule extends BlossomModuleSupport implements ModuleLifecycle {public void start(ModuleLifecycleContext moduleLifecycleContext) { initRootWebApplicationContext("classpath:/applicationContext.xml"); initBlossomDispatcherServlet("blossom", "classpath:/blossom-servlet.xml"); }
public void stop(ModuleLifecycleContext moduleLifecycleContext) { destroyDispatcherServlets(); closeRootWebApplicationContext(); } }
For more details on creating your own module see the documentation of the Module Mechanism.
Configuration
Create a configuration file for your ApplicationContext. Blossom doesn't require you to configure anything special in here. It does provide a namespace with a few tags that can come in handy later on. See sections Dependencies on Magnolia components and Configuring beans in the repository for more details.<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:blossom="http://www.magnolia-cms.com/schema/blossom" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.magnolia-cms.com/schema/blossom http://www.magnolia-cms.com/schema/blossom.xsd"></beans>
Add blossom-servlet.xml. This example uses Spring 2.5 component-scan to find the annotated classes. This example includes view resolvers for both JSP and Freemarker and selects which to use based on the extension. If you're only using one of them in your project you can reconfigure this and not have to return the extension from your controllers.
<?xml version="1.0" encoding="UTF-8"?> <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="info.magnolia.module.blossom.sample" use-default-filters="false"> <context:include-filter type="annotation" expression="info.magnolia.module.blossom.annotation.Template"/> <context:include-filter type="annotation" expression="info.magnolia.module.blossom.annotation.Area"/> <context:include-filter type="annotation" expression="info.magnolia.module.blossom.annotation.DialogFactory"/> </context:component-scan>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="customArgumentResolver"> <bean class="info.magnolia.module.blossom.web.BlossomWebArgumentResolver" /> </property> </bean>
<bean class="info.magnolia.module.blossom.preexecution.BlossomHandlerMapping"> <property name="targetHandlerMappings"> <list> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="useDefaultSuffixPattern" value="false" /> </bean> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /> </list> </property> </bean>
<bean class="info.magnolia.module.blossom.view.UuidRedirectViewResolver"> <property name="order" value="1" /> </bean>
<!-- JSP - renders all views that end with .jsp --> <bean class="info.magnolia.module.blossom.view.TemplateViewResolver"> <property name="order" value="2"/> <property name="prefix" value="/WEB-INF/templates/"/> <property name="viewNames" value="*.jsp"/> <property name="viewRenderer"> <bean class="info.magnolia.module.blossom.view.JspTemplateViewRenderer"/> </property> </bean>
<!-- Renders all views that end with .ftl --> <bean class="info.magnolia.module.blossom.view.TemplateViewResolver"> <property name="order" value="3"/> <property name="prefix" value="/WEB-INF/templates/"/> <property name="viewNames" value="*.ftl"/> <property name="viewRenderer"> <bean class="info.magnolia.module.blossom.view.FreemarkerTemplateViewRenderer"> <property name="contextAttributes"> <map> <entry key="cms"> <bean class="info.magnolia.rendering.renderer.ContextAttributeConfiguration"> <property name="name" value="cms"/> <property name="componentClass" value="info.magnolia.templating.freemarker.Directives"/> </bean> </entry> <entry key="cmsfn"> <bean class="info.magnolia.rendering.renderer.ContextAttributeConfiguration"> <property name="name" value="cmsfn"/> <property name="componentClass" value="info.magnolia.templating.functions.TemplatingFunctions"/> </bean> </entry> </map> </property> </bean> </property> </bean>
Reference
This is the reference documentation for Blossom. The sample code in this section uses Spring 2.5+ annotated controllers but this isn't necessary, you can use Blossom with plain controllers too.Templates
Exposing a controller as a template is no harder than adding@Template. Consider this example:
@Template(id = "myModule:pages/section" title = "Section Template") @Controller public class SectionController {@RequestMapping("/section") public ModelAndView handleRequest() { … } }
The id follows a convention used in Magnolia where you use the name of your module followed by a path. For templates that you want to use for pages the path part needs to start with pages.
Controlling where a template can be used
Sometimes you want to limit on what pages your template is available for editors to use. This is possible using the @Available annotation. It's used on a method in your controller that must return 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;
}
Areas
Areas are defined using nested classes (public static class) within a template or another area in which it belongs. They are normal Spring Web MVC controllers the same as templates and you annotate them with @Area. You can configure which components are available in them using the @AvailableComponents or @AvailableComponentClasses annotations. Areas have their own dialog.
An area can inherit components from areas of the same name in the page's parent pages. By default it will only inherit components marked for inheritance.
@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.jsp"; } }
// rest of the template excluded for brevity }
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 handleRequest() { … } }
@TemplateDescription is shown when you choose component to add in the page editor.
Dialogs
Blossom has a unique take on dialogs, it lets you create them using code. This has a few neat benefits. If your controller fetches data from say a web service, you could populate your dialog with options retrieved from that web service. For instance, 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. And also, you can easily change your dialog code in your IDE, compile and hot swap the new code into an instance of Magnolia CMS running in debug mode. This makes developing dialogs fast.Dialog factories
Dialog factories create dialogs. They're automatically exposed in Magnolia but you need to configure them as beans in Spring. The easiest way to do this is to use Spring 2.5+ component scan but manual configuration in XML also works.Have a look at this example:
@DialogFactory("front-page-dialog") @TabOrder({"Content", "Margins"}) public class FrontPageDialog {@TabFactory("Margins") public void margins(TabBuilder tab) { tab.addStatic("Margins around the side of the front page"); tab.addEdit("leftMargin", "Left Margin", "Left margin in pixels"); tab.addEdit("rightMargin", "Right Margin", "Right margin in pixels"); }
@TabFactory("Content") public void content(TabBuilder tab) { tab.addTitle("title", "Title", "The title of this page"); } }
front-page-dialog. It has two tabs, Content and Margins. The order that they appear in has been set using the @TabOrder annotation.
Methods annotated with @TabFactory in superclasses will be called. If you have a lot of dialogs that look the same you can create an abstract base class for them.
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. Like this:
@Template(dialog="my-dialog")
Templates can contain dialog factories
It's often the case that you design your template to be used with a few dialogs. Blossom allows you to create these within your template class to keep things in the same place. The concept is similar to dialog factories but here everything is done in a single method.@Template(id = "product-template", title="Product Template") public class ProductController {@DialogFactory("product-dialog") public void productDialog(DialogBuilder dialog) { TabBuilder settings = dialog.addTab("Settings"); settings.addEdit("title", "Title", ""); TabBuilder properties = dialog.addTab("Properties"); properties.addEdit("headline", "Headline", ""); } }
Validating dialog input
As an extra bonus of creating dialogs programmatically Blossom is able to provide you with a callback where you can validate the input that the editor has entered. This is done using the@TabValidator and @DialogValidator annotations.
Validation messages are set using Magnolia's AlertUtil class.
@TabValidator("Content") public void validateContent(DialogTab tab) { DialogEdit title = (DialogEdit) tab.getSub("title"); if (StringUtils.isEmpty(title.getValue())) { AlertUtil.setMessage("You need to enter a title!"); } }@DialogValidator public void validateDialog(Dialog dialog) { … }
@DialogFactory("main-properties") public void createDialog(Dialog dialog, DialogCreationContext context) { context.addValidator(new ValidationCallback() { public void validate(Dialog dialog) { … } }); }
Customizing how a dialog is saved
Sometimes its necessary to do post processing before the dialog is saved or to completely replace the built-in behavior with a custom solution. By annotating a method with the@InitSaveHandler annotation the method becomes responsible for creating and initializing a SaveHandler that will be used to save the dialog.
@InitSaveHandler public SaveHandler initSaveHandler() { return new CustomSaveHandler(); }
When the TabBuilder isn't enough
The TabBuilder has methods for adding all the standard controls included in Magnolia CE. When you have a custom control of your own or a control delivered by a third-party module or the enterprise edition this can seem like a serious limitation. Thankfully all you need to create controls on your own is available in DialogCreationContext which you can declare as an argument on your method and have passed to you. It's also possible to extend the dialog creation mechanism in Blossom and replace TabBuilder with a class of your own with methods for adding your custom or third-party controls.
The article Extending the Blossom TabBuilder on the wiki explains how this is done in detail.
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 areAggregationState, Context, WebContext, User and MgnlUser.
@RequestMapping("/book") public String handleRequest(@RequestParam("id") int id, Node pageNode, Node componentNode) { … }
This is achieved by configuring the BlossomWebArgumentResolver on your AnnotationMethodHandlerAdapter. This is included in the configuration example above.
View rendering
View rendering with Blossom is performed by Spring. Blossom comes with support for using the rendering chain in Magnolia both for JSPs and Freemarker. It works by having view resolvers that return views that call into Magnolia. As a result you'll have access to all the objects that Magnolia provides to template scripts.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();
And this example returns a redirect for a resource in the documents repository.
return "dms:" + content.getUUID();
The configuration example above shows how to set up the view resolvers.
If you substitute or combine the Blossom view resolvers with your own implementations then you need to keep in mind that the GZip filter included in Magnolia wraps the response object and gives it some odd semantics. It will tell you that the response is not committed even though it's been written to. It will also allow you to do redirects even though the response has been written to. While this might sometimes be a good thing you should know that if you write controllers that exploit this then you depend on the GZip filter and need to have it active. Some framework code, such as the JSP view rendering in 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.
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.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/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. For more details see the section below 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>
<blossom:pecid var="pecid" /> that will 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 />"
Handler Mapping
Spring uses HandlerMappings to map a request to a handler (usually a controller). Blossom interrogates the HandlerMappings to find out how you've mapped your handlers. Your HandlerMapping needs to be a class inherited fromAbstractUrlHandlerMapping. 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. The configuration example above shows how to do this.
You should also note that you have to explicitly declare your HandlerMapping in XML. Spring will supply you with defaults if you don't but Blossom can't get a reference to the defaults and therefore can't take part in the rendering.
Localization
Using@I18nBasename you can set the name of the resource bundle you want to use. It can be used on templates, areas and components and dialog factories.
If you want Spring to pick up the locale that Magnolia has selected for the current request Blossom provides you with a LocaleResolver that does exactly this.
info.magnolia.module.blossom.context.MagnoliaLocaleResolver
MessageSource implementation that bridges Magnolia i18n into Spring.
info.magnolia.module.blossom.context.MagnoliaMessageSource
Multipart Requests
Magnolia takes care of handling multipart requests and in order to use them Blossom provides a MultipartResolver implementation. It order to use it 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't need to configure them in the repository as you normally would.Blossom also 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 itself is not based on Spring, instead it uses a container internally that manages its long-lived components. While its simple enough to use that container and get access to components another approach that might be more comfortable for Spring developers is pulling them in as beans in the ApplicationContext where they'll be available for declaring dependencies on them and as candidates for autowiring.Example that exposes the ModuleManager as a bean with id 'moduleManager'. The id is optional.
<blossom:component id="moduleManager" type="info.magnolia.module.ModuleManager" />
For more details on Magnolia Components see the documentation on Configuration Mechanisms.
Configuring beans in the repository
The JCR repository is not only used for storing content for web pages. It's also widely used for configuration. One benefit of this is that configuration is easy to change while the application is running. For reading this configuration Magnolia uses a mechanism called Content2Bean. As the name implies it transforms 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 for instance dependencies in the bean that should be autowired by the ApplicationContext. The bean will also receive all the standard life cycle callbacks that a bean managed by Spring can use. These includeInitializingBean, ServletContextAware, ApplicationContextAware and many more. It can also use @PostConstruct and it will be subject for having AOP aspects applied.
The Content2Bean mechanism by default calls a method named 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's 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 service configured in the repository. The service handles discounts.
<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 Content2Bean see the documentation on Configuration Mechanisms.
Versions and compatibility
- Blossom requires Magnolia CMS 4.5 or later.
- Needs at least Java 6.
- Supports Spring Framework 2.5 or later.
Resources
Wiki - Blossom examples and articles
Blog - Tobias Mattsson
Tutorial - Spring Framework and Magnolia CMS: Creating Complex Java-based Websites
Conference talk - Integrating Magnolia with Spring Framework using Blossom at Magnolia Conference 2010
Webinar - US Navy Integrates Spring Applications with Flying Colors Thanks to Web CMS
Version 2.0 Javadoc Source cross-reference Source repository
Version 1.2.3 Javadoc Source cross-reference Source repository
Version 1.2.2 Javadoc Source cross-reference Source repository
Version 1.2.1 Javadoc Source cross-reference Source repository
Version 1.2 Javadoc Source cross-reference Source repository
Version 1.1.1 Javadoc Source cross-reference Source repository
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); } }
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.
Examples
Using pre-execution
This is a simple controller that displays a form. The title and description that will appear in the GUI have been set using annotations.@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) { if (StringUtils.isBlank(q)) return new ModelAndView("searchForm", "errorMessage", RepositoryUtils.getLocalNodeData("errorMessage")); String searchUrl = RepositoryUtils.getLocalNodeData("searchUrl"); return new ModelAndView(new RedirectView(searchUrl + q)); }
@TabFactory("Settings") public void createDialog(TabBuilder builder) { builder.addEdit("title", "Title", ""); builder.addEdit("bodyText", "Text", ""); builder.addEdit("searchUrl", "Search engine URL", "For instance: http://www.google.com/search?q="); builder.addEdit("errorMessage", "Error message", "For instance: You need to enter a query"); }
@TabValidator("Settings") public void validate(DialogTab dialogTab) { DialogEdit title = (DialogEdit) dialogTab.getSub("title"); if (StringUtils.isEmpty(title.getValue())) { AlertUtil.setMessage("You need to enter a title!"); } } }
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="cms-taglib" prefix="cms" %> <%@ taglib uri="blossom-taglib" prefix="blossom" %> <cms:editBar /> <h1>${content.title}</h1> <p><cms:out nodeDataName="bodyText" /></p> <c:if test="${not empty errorMessage}"> <p style="color:red;">${errorMessage}</p> </c:if> <form action="?" method="POST"> <blossom:pecid-input /> <input type="text" name="q" /> <input type="submit" value="Search" /> </form>
Release Notes
Version 2.0
Major update for Magnolia 4.5 and its new rendering engine, page editor and templating concepts. With Magnolia 4.5 areas are explicit entities rather than implicit as node collections. 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.@Paragraph has been deprecated and removed. Instead use @Template and set an id of the form <moduleName>:components/*
@ParagraphDescription has been renamed to @TemplateDescription
Templates now have a dialog, use the dialog annotations in the same way as you did for paragraphs.
Ids for templates and components are no longer generated from their @RequestMapping paths. As of this version they need to be specified on the @Template annotation explicitly.
Paragraphs used to require a dialog even if there was nothing to set. This is no longer the case, a component or template that doesn't use any of the annotations used to create a dialog will simply not have a dialog. A component is added to the page immediately without a dialog being shown.
Areas are defined using nested classes (public static class) within a template or another area in which it belongs. They are normal Spring Web MVC controllers the same as templates and you annotate them with @Area. You can configure which components are available in them using the @AvailableComponents or @AvailableComponentClasses annotations. Areas have their own dialog.
An area can inherit components from areas of the same name in the page's parent pages. By default it will only inherit components marked for inheritance.
The content API, centered on info.magnolia.cms.core.Content, has been deprecated. It's still around and fully backwards compatible and will be for some time. Instead we favour using the JCR API directly. Therefore in all the places where Blossom was giving you a Content object you can now instead choose to receive a javax.jcr.Node object. This includes all the methods you've annotated with blossom annotations as well as your @RequestMapping methods.
It's no longer necessary to use the ServletContextExposingContextListener as the functionality it provided is now part of Magnolia itself. The class has been removed, you need to update your web.xml.
The <blossom:configuration> that was previously required in your applicationContext.xml is no longer required and need to be removed.
Added features:
- BLOSSOM-65 Templates are dialog factories for their own dialog
- BLOSSOM-71 @TemplateDescription replaces @ParagraphDescription
- BLOSSOM-72 @Paragraph is replaced with @Template
- BLOSSOM-74 Templates are identified with id
- BLOSSOM-78 Area configuration using nested classes
- BLOSSOM-82 Areas can have available components set using the classes defining the components
- BLOSSOM-87 @Available methods can receive the TemplateDefinition for the template
- BLOSSOM-59 FactoryBeans for beans configured in JCR use JCR API
- BLOSSOM-61 BlossomWebArgumentResolver supports passing object being rendered as JCR Node instances
- BLOSSOM-79 ServletContextExposingContextListener is dropped
- BLOSSOM-81 Empty dialogs are not displayed
- BLOSSOM-83 Template ids needs to be set explicitly, generation from @RequestMapping is dropped
- BLOSSOM-84 Template title is set with title field instead of value field
- BLOSSOM-58 Update for Java 6 and JCR 2.0
- BLOSSOM-60 upgraded test to JUnit 4
- BLOSSOM-62 UuidRedirectView and resolver use JCR terminology (workspace instead of repository)
- BLOSSOM-63 MagnoliaComponentFactoryBean uses Components instead of FactoryUtil
- BLOSSOM-64 Templates are registered in Magnolia using providers
- BLOSSOM-67 Update rendering for Magnolia 4.5
- BLOSSOM-69 TemplateExporter is customizable in beans xml and have full control of the created definition
- BLOSSOM-70 DialogExporter is customizable in beans xml and have full control the the created definition
- BLOSSOM-73 Dialog registry is a magnolia component
- BLOSSOM-75 Dialogs are registered in Magnolia using providers
- BLOSSOM-76 Update template availability callbacks for Magnolia 4.5
Version 1.2.3
Extends the feature set of TabBuilder for adding multi select controls and introduces additional getters. Also makes it possible to directly add dialog descriptions to the dialog registry. This makes it possible to have new strategies for dialogs which is required by the new module magnolia-module-blossom-extension-data that adds support for using blossom dialogs with node types in the data module.Added features:
- BLOSSOM-43 Adds TabBuilder.addMultiSelect() addInclude() and addControl()
- BLOSSOM-50 Adds TabBuilder.getContext()
- BLOSSOM-53 Adds TabBuilder.getDialog()
- BLOSSOM-51 Dialog registry accepts dialog descriptions directly and DialogDescriptionBuilder is more open to extension
Version 1.2.2
Adds an enhancement to BlossomWebArgumentResolver that allows the content objects for both page and paragraph to be used as argument on annotated controllers. Also fixes two reported bugs.Added features:
- BLOSSOM-36 Support both page and paragraph content as arguments on controllers
- BLOSSOM-37 Empty dialog when editing version 0.5 content or using Magnolia before 4.3.2
- BLOSSOM-40 Model not emptied after rendering of paragraph completes
Version 1.2.1
Bug fix release. Fixes two issues related to context path and bridging from content rendering into Spring's DispatcherServlet and an issue with context path and uuid redirects.Bug fixes:
- BLOSSOM-33 - Simulated forwards should use the webapps context path
- BLOSSOM-34 - Simulated forward attributes are not visible after an include
- BLOSSOM-35 - Redirects based on uuid should include contextPath
Version 1.2
Big enhancements to the feature set and fixes two important bugs. Most notably added support for having beans that are configured in the repository with support for reloading them transparently when their configuration changes. The feature builds on an extension to Content2Bean that allows dependency injection, lifecycle events and applying AOP proxies using the ApplicationContext. As for the ApplicationContext there's a new feature that brings in singletons from Magnolia so that they can be specified as dependencies in bean definitions or used for autowiring. Also new in this release is a number of enhancements for working with content in controllers even easier.Added features:
- BLOSSOM-14 Make possible to set a SaveHandler for a dialog
- BLOSSOM-19 Support for DI and AOP in Content2Bean
- BLOSSOM-20 FactoryBeans for creating beans configured in repository with support for observed reloading
- BLOSSOM-23 WebArgumentResolver for Content, User, MgnlUser, AggregationState and Context
- BLOSSOM-24 FactoryBean that exposes magnolia singletons in ApplicationContext
- BLOSSOM-29 ViewResolver for sending redirects based on uuid
- BLOSSOM-30 Support for filter and servlet proxies that defer initialization until Magnolia has finished update/install phase
- BLOSSOM-31 @Available should be overridable in subclasses
- BLOSSOM-16 BlossomDispatcherServlet fails to render when handlerPath is a subset of the requestURI
- BLOSSOM-18 Multipart support doesn't work with Spring 3
- BLOSSOM-22 Move BlossomFilter deeper into filter chain to make sure that AggregationState is properly filled in during pre execution
- BLOSSOM-28 Upgrade to parent pom version 19
- BLOSSOM-32 Anonymous dialogs for paragraphs should be registered in repository like normal dialogs
Version 1.1.1
Small feature and bug fix release. The javadoc documentation has been greatly improved and extended. You can now annotate a method in your templates to control where the template is available. Blossom now also support multipart requests by exposing Magnolias built in support through the Spring APIs. VirtualURIMappings are now discovered and automatically exported. There's also support for declaring them using an annotation @VirtualURIMapper. Also, if you've done customizations to add more argument types to methods for creating and validating dialogs and tabs you'll be pleased to see that this extension point is now more comprehensible and easy to use.Added features:
- BLOSSOM-1 Support for @Available to control where a template can be used
- BLOSSOM-4 Support for exposing VirtualURIMappings and classes using @VirtualURIMapper
- BLOSSOM-11 Support for multipart requests, bridges Springs abstraction onto Magnolia multipart processing
- BLOSSOM-5 NPE while detecting dialog factories and encounters abstract bean
- BLOSSOM-7 ContextLoader is not released correctly on module reload
- BLOSSOM-12 The autodetected node is created only when needed so it won't be considered as a dialog when its empty
- BLOSSOM-13 Use of configNode when creating dialogs
- BLOSSOM-8 Upgrade POM expand javadoc and pass checkstyle tests
- BLOSSOM-9 Improve package structuring and extract detection of templates, paragraphs and dialogs to separate classes
Version 1.1
Blossom is now maintained and supported by Magnolia International Ltd. Package names and XML namespace URIs change to reflect this. The Maven artifact is also changed toinfo.magnolia. Apart from this the release is a minor release with an important bug fix that prevented dialogs from working in Magnolia CMS 4.3.2. Also adds support for initializing Spring within modules, adding radio button controls to dialogs and setting visibility on templates.
Added features:
- Support for properly getting Spring initialization within module install and startup
- Added radio button control to
TabBuilder - Added support for visibility on templates
- Fix for Magnolia CMS 4.3.2, the paragraph edit dialog was broken due to changes in Magnolia CMS
TabBuilder.addFileno longer throwsRepositoryException- Methods in
TemplateDescriptionBuilderchanged from private to protected - Tabs added to dialogs have their name set to match their label
- Maven artifact
groupIdis nowinfo.magnolia - Package names changed from
se.issi.magnolia.toinfo.magnolia. - Spring namespace changed from
http://www.issi.se/schema/blossomtohttp://www.magnolia-cms.com/schema/blossom
- Custom paragraph select dialog is removed
- Blossom pre-execution filter enabled by default on install
Version 1.0
As of version 1.0 Blossom gives you a much bigger feature set and some of the functionality in the previous version has matured. Unfortunately this means that some changes have to be made when upgrading. But don't worry, your content does not have to be touched. Added features:- Support for exposing controllers as Templates
- Support for creating all types of dialogs with code
- Localization support for paragraphs, dialogs and templates
- Rendering of views is performed using the Magnolia CMS render chain, supports both JSP and Freemarker
- Ordering of dialog tabs is customizable with
@TabOrder - The locale used by Magnolia can be made visible to Spring using
MagnoliaLocaleResolver - Magnolia's localization mechanism can be bridged into Spring using
MagnoliaMessageSource - JSP tag
pecid-inputis now also available aspecidInput
- Blossom now exposes controllers are normal paragraphs instead of using a placeholder paragraph
- Customization of the paragraph select dialog is no longer necessary.
- Paragraphs are no longer named after the
handlerPaththey're mapped to. That is, a controller mapped to/textthat was previously referred to in templates as/textis now simplytext. This had to be done in order to support migration of existing configured paragraphs into Blossom. Using a forward slash/in a paragraph name was never a good idea in the first place. You don't have to specify a name on your paragraph, it will automatically use thehandlerPathstripped from any slashes as its name. @ParagraphTitleis renamed to@Paragraphand moves tose.issi.magnolia.module.blossom.annotation@ParagraphDescriptionmoves tose.issi.magnolia.module.blossom.annotationDialogTabBuilderis renamed toTabBuilderDialogTabBuilder.addTextchanges toTabBuilder.addEdit- Methods in
DialogTabBuilderno longer throwRepositoryException - Methods in
DialogTabBuildernow return the control they create @DialogTabFactoryis renamed to@TabFactoryand moves tose.issi.magnolia.module.blossom.annotation- Ordering of tabs in dialogs are now set using the
@TabOrderannotation <blossom:handler-mapping />is deprecated and removed<blossom:paragraph-registry />is renamed to<blossom:configuration />- Extension hooks in
DefaultBlossomParagraphRegistryhave new arguments ValidatingParagraphis removed and replaced with@DialogValidator- Interface
DialogCreatorhas changed andDefaultDialogCreatorchanges accordingly.