http://www.developer.com/java/web/integrating-advanced-spring-framework-features-with-magnolia-cms.html

Back to article

Integrating Advanced Spring Framework Features with Magnolia CMS


December 13, 2010

Magnolia CMS, an open-source, enterprise-grade CMS written entirely in Java, has recently added Spring support, allowing Spring developers to easily extend the base CMS with custom functionality using their existing knowledge of the Spring Framework. In my previous article, Spring Framework and Magnolia CMS: Creating Complex Java-based Websites I introduced Blossom, the Magnolia CMS module for Spring, and illustrated how to use it to integrate Spring controllers and views into the Magnolia architecture.

The instructions in that tutorial were just the tip of the iceberg, however. With Blossom bridging the Spring Framework and Magnolia CMS, all manner of interesting Java-based Web development options become available. For example, Spring developers can validate form input, incorporate the Spring Form Tag Library, and create dialogs. In this tutorial, I dive into these more advanced examples.

Note: To learn the basics of Blossom, read my previous Spring Framework and Magnolia CMS article.

Validating Form Input with the Spring Framework and Magnolia CMS

A common requirement of complex Java-based websites is to apply custom validation to the input submitted through a Web form. With Magnolia's Spring support, this is a snap to implement because Spring already provides all the components needed for Web form input validation and error display.

The typical way to validate form input with Spring is to create a separate class that implements the Validator interface, and bind this to a POJO that represents the form input. If the form is created using Spring's form tab library, input errors detected by the validator class can be automatically displayed in the form using the tag. In case you've never done this before, take a look at this introductory material to understand the process before proceeding with the rest of this tutorial.

To begin, define a POJO with setter and getter methods for the form fields, as shown in the pizza design form below.

package info.magnolia.module.example;
/**
* Bean representing the pizza design form
*/
public class PizzaForm {
private String name;
private String email;
private String message;
private Integer size;
private String[] toppings;
private String crust;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}

public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}

public String getCrust() {
return crust;
}
public void setCrust(String crust) {
this.crust = crust;
}
public String[] getToppings() {
return toppings;
}
public void setToppings(String[] toppings) {
this.toppings = toppings;
}
}

Save this file as src/main/java/info/magnolia/module/example/PizzaForm.java.

Next, create a validator class that implements the Validator interface and contains the validation rules for your form, which will perform email address validation using regular expressions.

package info.magnolia.module.example;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* Validator for validating forms
*/
public class PizzaFormValidator implements Validator {
public boolean supports(Class clazz) {
return clazz.equals(PizzaForm.class);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required", "Name is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "required", "Email address is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "message", "required", "Message is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "crust", "required", "Crust type is required");
if (((PizzaForm)target).getToppings().length < 1) errors.rejectValue("toppings", "invalid", "At least one topping must be selected");
if (!isValidEmailAddress(((PizzaForm)target).getEmail())) errors.rejectValue("email", "invalid", "Email address is invalid");
}

public boolean isValidEmailAddress(String emailAddress){
String expression="^[\w\-]([\.\w])+[\w]+@([\w\-]+\.)+[A-Z]{2,4}$";
CharSequence inputStr = emailAddress;
Pattern pattern = Pattern.compile(expression,Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(inputStr);
return matcher.matches();
}
}

As explained in the Spring reference guide, this class implements the supports() and validate() methods of the Validator interface.

The validate() method is the real workhorse here; it uses various methods from the ValidationUtils class to validate and reject invalid form input. In particular, note the rejectIfEmptyOrWhitespace() method, which can be used to ensure that required fields are not empty, and the rejectValue() method, which can be used to reject fields that fail to satisfy specified criteria. The rejectValue() method is particularly handy for custom validation tasks, such as email address validation using regular expressions (as shown in the example above).

So now we have a validator and a POJO, all that's left is to link them together. To do this, update the PizzaFormParagraph object and bind the validator to the POJO, as shown below:

package info.magnolia.module.example;
import info.magnolia.module.blossom.annotation.Paragraph;
import info.magnolia.module.blossom.annotation.ParagraphDescription;
import info.magnolia.module.blossom.annotation.TabFactory;
import info.magnolia.module.blossom.dialog.TabBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.List;
import java.util.ArrayList;
import info.magnolia.cms.core.Content;
import info.magnolia.context.WebContext;
import info.magnolia.context.MgnlContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* Displays a pizza order form and a confirmation page after the form is submitted.
*/
@Controller
@Paragraph(value="Pizza Form", name="pizzaForm")
@ParagraphDescription("A form for users to order a pizza")
public class PizzaFormParagraph {
private static final String[] toppings = new String[] {"Cheese", "Tomato", "Onion", "Ham", "Bacon", "Anchovies"};
@RequestMapping("/pizza/new")

public String handleRequest(ModelMap model, @ModelAttribute PizzaForm pizzaForm, BindingResult result, HttpServletRequest request) {
Content content = MgnlContext.getWebContext().getAggregationState().getCurrentContent();
List x = new ArrayList();
for (String t : toppings) {
x.add(t);
model.addAttribute("availableToppings", x);
}
if ("POST".equals(request.getMethod())) {
new PizzaFormValidator().validate(pizzaForm, result);
if (result.hasErrors()) {
return "pizzaForm";
}
return "pizzaFormResult";
}
return "pizzaForm";
}

@TabFactory("Content")
public void contentTab(TabBuilder tab) {
tab.addStatic("This paragraph requires no configuration");
tab.addHidden("bogus", "");
}
}

Notice that the controller now specifies the list of toppings as an array, and makes this array available to the template script as an attribute named availableToppings. Why? Keep reading.

Using Spring's Form Tag Library with the Magnolia CMS

With the validation taken care of, there's one final step left. The JSP template must be updated to display error messages for fields that fail validation, so that users know what went wrong and can correct their input before re-submitting the form. When again, Spring comes to the rescue with its form tag library, which provides a set of tags to dynamically create Web forms. This tag library can also directly access controller properties, making it possible to easily inject data such as selection lists or error messages from the controller.

To see how this works, update your pizzaForm.jsp template to look like this:

<!--taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"-->
<!--taglib uri="cms-taglib" prefix="cms"-->
<!--taglib uri="blossom-taglib" prefix="blossom"-->
<!--taglib uri="http://www.springframework.org/tags/form" prefix="form"-->
<!--

.error {
color:red;
}

-->
<h2>Design a Pizza</h2>
Size:

Toppings:

Crust:
Thin and crisp
Thick and cheesy

Name:

Email address:

Notes:

<input name="submit" type="submit" value="Save" />

You can read all about the tag library here, but two important things to note about this template now are:

  1. The tag that dynamically produces a list of checkboxes from an array -- In this case, the availableToppings array defined earlier in the controller
  2. The tag that provides a way to display errors generated by the validator -- The path attribute serves as a key to look up the error message associated with that particular key.
Tip: If you prefer to display errors in a block at the top of the form, instead of under individual fields, use the '*' wildcard as your error path. Look here for more information.

Figure 1 shows what your project directory should now look like.

Spring Framework and Magnolia CMS
Figure 1:
Final Project Directory Structure

You should now recompile the module and then tell Magnolia to reinstall it. To do this, switch to the AdminCentral -> Configuration menu and, in the module list, find and delete the modules/example node (as shown in Figure 2).

Spring Framework and Magnolia CMS
Figure 2:
Magnolia CMS Module Deletion

Next, stop the server, copy the new module JAR file from the project's example-module/target directory to the WEB-INF/lib directory of the Magnolia authoring instance, and restart the server. Magnolia should offer to reinstall the module for you. Accept the offer, and then try revisiting the pizza form page and entering invalid data into the form. You should see a list of error messages as the validator detects and alerts you to the invalid items. Figure 3 shows what it looks like.

Spring Framework and Magnolia CMS
Figure 3: Validation of Invalid Form Input

The success page will now be displayed only after you enter valid data for all the fields. Try it yourself and see!

Creating Dialogs with the Spring Framework and Magnolia CMS

Magnolia templates also support user-level configuration through the use of dialogs. A dialog is essentially a configuration "menu" for a template, allowing authors to exert control over different aspects of the template, such as the title, orientation, or display attributes. Normally, dialogs need to be manually defined and associated with a template, as shown in the Magnolia templating tutorial. This can be tedious and time-consuming, especially with complex templates. Blossom and Spring however, make the entire exercise much simpler and easier to handle by allowing dialogs to be defined programmatically.

The main component here is Blossom's TabBuilder component, which provides a set of methods to create dialogs for paragraph templates. A key benefit of this is that it enables dialogs to become more dynamic, with dialog options specified at run-time rather than at design-time; an ancillary benefit is that since dialogs are now defined via code, changes to them can be tracked through regular source control systems.

Let's see how this works by enhancing the paragraph template to allow authors to define which pizza toppings are available for user selection. Update your paragraph definition as shown below:

package info.magnolia.module.example;
import info.magnolia.module.blossom.annotation.Paragraph;
import info.magnolia.module.blossom.annotation.ParagraphDescription;
import info.magnolia.module.blossom.annotation.TabFactory;
import info.magnolia.module.blossom.dialog.TabBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.List;
import java.util.ArrayList;
import info.magnolia.cms.core.Content;
import info.magnolia.context.WebContext;
import info.magnolia.context.MgnlContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* Displays a pizza order form and a confirmation page after the form is submitted.
*/
@Controller
@Paragraph(value="Pizza Form", name="pizzaForm")
@ParagraphDescription("A form for users to order a pizza")
public class PizzaFormParagraph {
private static final String[] toppings = new String[] {"Cheese", "Tomato", "Onion", "Ham", "Bacon", "Anchovies"};
@RequestMapping("/pizza/new")

public String handleRequest(ModelMap model, @ModelAttribute PizzaForm pizzaForm, BindingResult result, HttpServletRequest request) {
Content content = MgnlContext.getWebContext().getAggregationState().getCurrentContent();
List x = new ArrayList();

for (String t : toppings) {
if (content.getNodeData(t).getString().equals("true")) {
x.add(t);
model.addAttribute("availableToppings", x);
}
}
if ("POST".equals(request.getMethod())) {
new PizzaFormValidator().validate(pizzaForm, result);
if (result.hasErrors()) {
return "pizzaForm";
}
return "pizzaFormResult";
}
return "pizzaForm";
}

@TabFactory("Content")
public void contentTab(TabBuilder tab) {
for (String t : toppings) {
tab.addCheckbox(t, t, "Check this box to make " + t + " a selectable topping");
}
}
}

The most important change here is in the contentTab() method, which now iterates over the toppings array and creates a dialog containing a checkbox for each topping. This allows authors to define at run-time which toppings should actually appear in the output form. Within the handleRequest() method, a few additional lines of code implement a conditional test, adding only the selected toppings to the availableToppings attribute that is read by the JSP template script.

Now, compile and redeploy the module to Magnolia CMS. Preview the example page and add a new pizza form. This time, you should see a dialog that asks you to specify which toppings should appear in the output template, as shown in Figure 4.

Spring Framework and Magnolia CMS
Figure 4:
Template Configuration Dialog

The pizza form will now be configured with only the selected toppings, as shown in Figure 5.

Spring Framework and Magnolia CMS
Figure 5:
Form Template Configured as Per Dialog Input

Tip: It's also possible to validate dialog selections using Spring, just like you would validate regular form input. Look here for some sample code.

Conclusion

As these examples illustrate, Magnolia's Blossom module makes it easy to integrate advanced features of the Spring framework with Magnolia's existing templating architecture. This allows developers a great deal of creativity and flexibility when building complex Web sites with custom workflows and non-standard use cases. Try it out sometime and see how easy it is.

Code Download

  • Magnolia-Spring_src
  • For Further Reading

  • Spring Framework and Magnolia CMS: Creating Complex Java-based Websites (from Developer.com)
  • Set Up the Magnolia CMS for Web Content Creation in Just a Few Clicks (from WebReference)
  • Publish Web Content Efficiently with Magnolia CMS (from WebReference)
  • The Magnolia CMS templating tutorial (from documentation.magnolia-cms.com)
  • About the Author

    Vikram Vaswani is a consultant specializing in open-source tools and technologies. He has more than 11 years of experience deploying open-source tools in corporate intranets, high-traffic Web sites, and mission-critical applications. He is a frequent contributor of articles and tutorials to the open source community, and the author of six books on PHP, MySQL and XML technologies.

    Sitemap | Contact Us