| Your Rating: |
![]() ![]() ![]() ![]()
|
Results: |
![]() ![]() ![]() ![]()
|
27 | rates |
| Moved! Blossom documentation has moved to official documentation site. This wiki page will be removed shortly. Please don't add new material here. Coordinate edits to the official doc through Antti. |
| As of version 1.1 Magnolia Blossom is maintained and supported by Magnolia International Ltd. |
Key functionality
- Annotation based API where annotated classes are automatically detected and registered in Magnolia. Simply add @Template to your controller and it will be ready to be used by editors.
- Exposes controllers as paragraphs and templates, 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.
- 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 paragraph in-front of Magnolia 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.
License
Uses the same dual license model as Magnolia. GPLv3 and Magnolia Network Agreement.
Issue tracker
http://jira.magnolia-cms.com/browse/BLOSSOM
Available in the Magnolia maven repository
http://repo.magnolia-cms.com/m2/
Table of contents
- Table of contents
- Programming model
- Getting started
- Installation
- Starting Spring in your own module (recommended!)
- Starting Spring from web.xml
- Configuration
- Reference
- Templates
- Paragraphs
- Dialogs
- View rendering
- Pre-execution of paragraphs
- Handler Mapping
- Localization
- Extension points
- Versions and compatibility
- Using Spring WebFlow inside Magnolia
- Appendix A: Examples
- Appendix B: Release Notes
Programming model
This is a brief example of how it's used, these classes are automatically exposed in Magnolia
@Paragraph("Text") @Controller public class TextParagraph { @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("Main") @Controller public class MainTemplate { @DialogFactory("frontpage-properties") public void addDialog(DialogBuilder dialog) { DialogTabBuilder tab = dialog.addTab("Properties"); tab.addEdit("headline", "Headline", "The text to use as a headline"); } @RequestMapping("/main") public String handleRequest() { return "mainTemplate.ftl"; } } @DialogFactory("main-properties") public class MainDialogFactory { @TabFactory("Properties") public void addPropertiesTab(TabBuilder tab) {} @DialogValidator public void validate(Dialog dialog) {} }
Getting started
Installation
The first thing you need to do is download Blossom from https://sourceforge.net/projects/magnoliablossom/. Drop this jar into the WEB-INF/lib directory of your application.
Maven users can find the artifact in the magnolia maven repository.
<repository>
<id>magnolia</id>
<url>http://repo.magnolia-cms.com/m2/</url>
</repository>
<dependency>
<groupId>info.magnolia</groupId>
<artifactId>magnolia-module-blossom</artifactId>
<version>1.1</version>
</dependency>
Starting Spring in your own module (recommended!)
This is the recommended way to start Spring since it will postpone initialization when Magnolia is in install/update mode.
Add this to your web.xml. Make sure you put it before Magnolias context listener.
<listener> <listener-class>info.magnolia.module.blossom.support.ServletContextExposingContextListener</listener-class> </listener>
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(); } }
Starting Spring from web.xml
| This approach has some serious issues You should really consider starting Spring inside your own module (see above). The reason for this is that when Magnolia decides it needs to install or update modules Spring will try to start anyway and will often encounter an error since dependencies on Magnolia will fail when in install/update mode. |
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>blossom</servlet-name> <servlet-class>se.issi.magnolia.module.blossom.render.BlossomDispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
Configuration
Add the blossom configuration to your applicationContext.xml
<?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-2.5.xsd http://www.magnolia-cms.com/schema/blossom http://www.magnolia-cms.com/schema/blossom-1.1.xsd"> <blossom:configuration /> </beans>
Add blossom-servlet.xml. This example uses Spring 2.5 component-scan to find the annotated classes. The view resolvers return views for rendering JSP's with Magnolia's JSP renderer. Change the renderers if you're using freemarker.
<?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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:annotation-config/> <context:component-scan base-package="se.issi.magnolia.module.blossom.sample" use-default-filters="false"> <context:include-filter type="annotation" expression="se.issi.magnolia.module.blossom.annotation.Paragraph"/> <context:include-filter type="annotation" expression="se.issi.magnolia.module.blossom.annotation.Template"/> <context:include-filter type="annotation" expression="se.issi.magnolia.module.blossom.annotation.DialogFactory"/> </context:component-scan> <bean class="se.issi.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="se.issi.magnolia.module.blossom.view.TemplateViewResolver"> <property name="prefix" value="/WEB-INF/views/templates/"/> <property name="suffix" value=".jsp"/> <property name="viewRenderer"> <bean class="se.issi.magnolia.module.blossom.view.JspTemplateViewRenderer" /> </property> </bean> <bean class="se.issi.magnolia.module.blossom.view.ParagraphViewResolver"> <property name="prefix" value="/WEB-INF/views/paragraphs/"/> <property name="suffix" value=".jsp"/> <property name="viewRenderer"> <bean class="se.issi.magnolia.module.blossom.view.JspParagraphViewRenderer" /> </property> </bean> </beans>
Reference
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("Section Template") @Controller public class SectionTemplate { @RequestMapping("/section") public ModelAndView handleRequest() { ... } }
This controller will be exposed in magnolia and will show up as 'Section Template'.
The name of the template (used internally by magnolia) will be generated from the path that maps to this controller, in this case it will be 'section'. It's taken from the @RequestMapping annotation and had the leading '/' removed. Slashes inside the path will be replaced with '_'. You can change this behavior by setting the name directly on the class like this:
@Template(value="Section Template", name="my-section-template")
Paragraphs
Controllers annotated with @Paragraph are exposed in magnolia in the same way as templates are, consider this example:
@Paragraph("Text and Image") @ParagraphDescription("Adds a text section with an image") @Controller public class TextAndImageParagraph { @RequestMapping("/textAndImage") public ModelAndView handleRequest() { ... } }
This controller will be visible as the paragraph 'Text and Image'. The description set using @ParagraphDescription is shown in the select paragraph dialog.
The name of the paragraph, that you specify in templates to have it selectable, will be generated from the path that maps to this controller, in this case it will be 'textAndImage'. It's taken from the @RequestMapping annotation and had the leading '/' removed. Slashes inside the path will be replaced with '_'. You can change this behavior by setting the name directly on the class like this:
@Paragraph(value="Text and Image", name="text-and-image")
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 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"); } }
A dialog will registered in magnolia with the name '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.
Paragraphs create their own dialog
A paragraph 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 paragraph 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 for this paragraph. Like this:
@Paragraph(dialog="my-dialog")
| Magnolia won't save a paragraph that has an empty dialog To get around this add a hidden control that saves some bogus value. @TabFactory("Content") public void addTab(TabBuilder tab) { tab.addStatic("This paragraph requires no configuration"); tab.addHidden("bogus", "bogus"); } |
Templates can contain dialog factories
It's often the case 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("product-template") public class ProductTemplate { @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 programatically 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. This is possible in both paragraphs and dialog factories.
Validation messages are set using Magnolias 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) { ... }
Adding validators in dialog factories that are nested inside templates.
@DialogFactory("main-properties") public void createDialog(Dialog dialog, DialogCreationContext context) { context.addValidator(new ValidationCallback() { public void validate(Dialog dialog) { ... } }); }
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. This is done with two view resolvers, one for templates and one for paragraphs. They return views that call into magnolia. The point being that you usually need to have access to the goodies that magnolia exposes to you, for instance for accessing the repository.
| The GZip filter might affect rendering The GZip filter wraps the response object and gives it some strange 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 you depend on the GZip filter and you 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 paragraphs
Pre-execution allows you to write paragraphs 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 a 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 server/filters/blossom in the configuration repository 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 above 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 paragraph 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="<paragraph 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>
There's also <blossom:pecid var="pecid" /> that will set the paragraph uuid as a variable in this example named pecid. The var attribute is optional, if it's not specified the paragraph uuid is written directly into the page.
<a href="/news/new-website-launched.html?_pecid=<blossom:pecid />"
At the end of this article you'll find a full example of how to use pre-execution.
Handler Mapping
Spring uses HandlerMapping's to map a request to a handler (usually a controller). Blossom interrogates the HandlerMapping's to find out how you've 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. The configuration example above shows how to do this.
You should also note that you have to declare your HandlerMapping in xml. Spring will supply you with defaults if you don't declare your own but Blossom can't get a reference to the defaults and therefore can't find the controllers to export.
Localization
Using @I18nBasename you can set the name of the resource bundle you want to use. It can be used on paragraphs, templates 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.
se.issi.magnolia.module.blossom.context.MagnoliaLocaleResolver
There's also a MessageSource implementation that bridges magnolia i18n into Spring.
se.issi.magnolia.module.blossom.context.MagnoliaMessageSource
Extension points
Many things in Blossom is open for extension. You'll need to consult the source for this. To get you started you need to know that <blossom:configuration /> is short for:
<bean id="blossomConfiguration" class="se.issi.magnolia.module.blossom.BlossomConfiguration" />
This bean has setters for plugging in custom implementations of the internal classes.
Versions and compatibility
- Blossom has been tested on magnolia 4.1, 4.2 and 4.3.
- Requires Java 5 and has been tested for Java 6.
- Spring Framework 2.5 and 3.0.
Using Spring WebFlow inside Magnolia
It is possible to include a webflow as a paragraph and embed it into a page. The example below is a controller that exposes a booking flow.
@Controller @Paragraph("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); } // This is necessary since Magnolia won't add paragraphs with empty dialogs @TabFactory("Content") public void addTab(TabBuilder tab) { tab.addStatic("This paragraph requires no configuration"); tab.addHidden("bogus", "bogus"); } }
There are a few things you need to know though. You have 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.
Appendix A: Examples
Using pre-execution
This is a simple controller that displays a form. The title and description that will appear in the gui has been set using annotations.
@Controller @RequestMapping("/search") @Paragraph("Search form") @ParagraphDescription("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!"); } } }
And this is the JSP:
<%@ 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>
Appendix B: Release Notes
Version 1.1
Blossom is now maintained and supported by Magnolia International Ltd. Package names and xml
namespace uris changes to reflect this. The maven artifact is also changed to info.magnolia.
Apart from this the release is a minor release with an important bug fix that prevented
dialogs from working in Magnolia 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
Bug fixes:
- Fix for Magnolia 4.3.2, the paragraph edit dialog was broken due to changes in Magnolia
- TabBuilder.addFile no longer throws RepositoryException
- Methods in TemplateDescriptionBuilder changed from private to protected
- Tabs added to dialogs have their name set to match their label
API Changes:
- Maven artifact groupId is now info.magnolia
- Package names changed from se.issi.magnolia.* to info.magnolia.*
- Spring namespace changed from http://www.issi.se/schema/blossom to http://www.magnolia-cms.com/schema/blossom
Behind the scenes:
- Custom paragraph select dialog is removed
- Blossom preexecution 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 don't 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 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-input is now also available as pecidInput
Behind the scenes:
- Blossom now expose controllers are normal paragraphs instead of using a placeholder paragraph
that delegates rendering to the controller. This is a much cleaner design and opens for migration
of existing content and paragraphs into using blossom. - Customization of the paragraph select dialog is no longer necessary.
API Changes:
- Paragraphs are no longer named after the handlerPath they're mapped to. I.e, a controller mapped
to /text that was previously referred to in templates as '/text' is now simply 'text'. This had to
be done in order to support migration of existing configured paragraphs into blossom. Using / 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 the handlerPath stripped from any slashes as it's name. - @ParagraphTitle is renamed to @Pargraph and moves to se.issi.magnolia.module.blossom.annotation
- @ParagraphDescription moves to se.issi.magnolia.module.blossom.annotation
- DialogTabBuilder is renamed to TabBuilder
- DialogTabBuilder.addText changes to TabBuilder.addEdit
- Methods in DialogTabBuilder no longer throws RepositoryException
- Methods in DialogTabBuilder now return the control they create
- @DialogTabFactory is renamed to @TabFactory and moves to se.issi.magnolia.module.blossom.annotation
- Ordering of tabs in dialogs are now set using the @TabOrder annotation
- <blossom:handler-mapping /> is deprecated and removed
- <blossom:paragraph-registry /> is renamed to <blossom:configuration />
- Extension hooks in DefaultBlossomParagraphRegistry has new arguments
- ValidatingParagraph is removed and replaced with @DialogValidator
- Interface DialogCreator has changed and DefaultDialogCreator changes accordingly.
Version 0.5
Initial release.

Comments (4)
Nov 05, 2009
Roelof Jan says:
Great to have some Spring integration! It would be nice to have a maven artifac...Great to have some Spring integration!
It would be nice to have a maven artifact as well. I couldn't find any.
Something to consider might be the use of the ContextLoaderServlet. As far as I now this is no longer available in Spring 3.
See the 2.5 apidoc:
"Note that this class has been deprecated for containers implementing Servlet API 2.4 or higher, in favor of ContextLoaderListener.
According to Servlet 2.4, listeners must be initialized before load-on-startup servlets. Many Servlet 2.3 containers already enforce this behavior. If you use such a container, this servlet can be replaced with ContextLoaderListener."
Mar 26
Lutz Hühnken says:
I am planning on using Blossom in a project and so far it looks pretty good. Two...I am planning on using Blossom in a project and so far it looks pretty good. Two remarks:
We configured the org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver as the view resolver in paragraphs-servlet.xml, and it seems to work fine? Is there a reason why it should not?
Anyway, very interesting module, I'm looking forward to using it and it thinks it will allow us a nice, clean architecture.
Mar 26
Tobias Mattsson says:
That should work fine, however you'll want to have the directives setup in freem...That should work fine, however you'll want to have the directives setup in freemarker the way they are with magnolia. This can be done by having a viewresolver that returns a view that renders using magnolia's FreemarkerHelper.
There's alot of activity in trunk right now. The 1.0 release will be ready next month. Among the features are:
It's very exciting to see all this come together. The stuff in trunk is subject to change right now but if you have the time you're welcome to check it out. We would love to hear what you have to say.
The upcoming version will be compatible with 0.5 produced content. There will be some changes to the API but nothing that you can't fix with a few search-replace operations.
Jul 15
Thomas Duffey says:
Is it possible to associate a SaveHandler with a Blossom configured Dialog? ...Is it possible to associate a SaveHandler with a Blossom configured Dialog? On standard dialogs you can add a "saveHandler" attribute with a class name as the value and it will be executed when the user saves the dialog.
This is sort of like validation but there are parameters available to the SaveHandler that are not available in just the request. For example, a SaveHandler has access to the MultipartForm which allows you to do things with files that have been uploaded.