Using Command Controllers

Up until now, we have talked about the ways to get the data to the user based on the request parameters and how to render the data passed from the controllers. A typical application also gathers and processes data from the user. Spring supports this scenario by providing command controllers that process the data posted to the controllers. Before we can start discussing the various command controllers, we must examine the concept of command controllers.

The command controller allows a command object's properties to be populated from the form submission. The command controllers closely work with the Spring tag library to simplify data validation. The command controller is an ideal place to perform all business validation. As the validation occurs on the server, it is impossible for the users to bypass the validation, but you should not rely on the Web Tier to perform all validation, and you should revalidate in the Business Tier.

On the technical side, the command controller implementations expose a so-called command object, which is (in general) a domain object. Let's take a look at the command controllers we can use in our application.

AbstractCommandController: The first member of the command controller inheritance hierarchy we discuss is AbstractCommandController. Just like AbstractController, it implements the Controller interface. This class is not designed to actually handle HTML form submissions, but it provides basic support for validation and data binding. You can use this class to implement your own command controller in case the Spring controllers are insufficient for your needs.

AbstractFormController: The AbstractFormController class extends AbstractCommandController and can actually handle HTML form submission. In other words, this command controller processes the values in HttpServletRequest and populates the controller's command object. The AbstractFormController also has the ability to detect duplicate form submission, and it allows you to specify the views that are to be displayed in the code rather than in the Spring context file.

SimpleFormController: This is the most commonly used command controller to process HTML forms. It is also designed to be very easy to use—you can specify the views to display for the initial view and a success view, and you can set the command object you need to populate with the submitted data.

AbstractWizardFormController: As the name suggests, this command controller is useful for implementing a wizard style set of pages. This also implies that you must keep the command object in the current HttpSession and you need to implement the validatePage() method to check whether the data on the current page is valid and whether the wizard can continue to the next page. In the end, AbstractWizardFormController executes the processFinish() method to indicate that it has processed the last page of the wizard process and that the data is valid and can be passed to the Business Tier.

Using Form Controllers

Now that you know which form controller to choose, let's create an example that demonstrates how a form controller is used. We start with the simplest form controller implementation and then move on and add validation and custom formatters.

The most basic controller implementation extends SimpleFormController, overrides its onSubmit() method, and provides a default constructor, as shown in Listing 17-23.

Listing 17-23: ProductFormController Implementation

image from book
package com.apress.prospring.ch17.web.product;      public class ProductFormController extends SimpleFormController {          public ProductFormController() {         super();         setCommandClass(Product.class);         setFormView("products-edit");     }          protected ModelAndView onSubmit(HttpServletRequest request,          HttpServletResponse response, Object command,          BindException errors) throws Exception {         System.out.println(command);              return new ModelAndView("products-index-r");     }      } 
image from book

The ProductFromController's constructor defines that the command class is Product.class; this means that the object this controller creates is an instance of Product.

Next, we override the onSubmit() method, which gets called when the user submits the form. The command object has already passed the validation and it is safe to pass it to the Business Tier of the application, if appropriate. The onSubmit() method returns a products-index-r view, which is a RedirectView that redirects to the products/index.html page. We need this because we want the ProductController.handleIndex() method to take care of the request.

Finally, the call to setFormView() specifies the view that is used to display the form. In our case, it is a JSP page, as shown in Listing 17-24.

Listing 17-24: edit.jsp Page

image from book
<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%> <%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>      <html> <head>     <c:set var="css"><spring:theme code="css"/></c:set>         <c:if test="${not empty css}">             <link rel="stylesheet"                   href="<c:url value="${css}"/>" type="text/css" />         </c:if> </head> <body> <form action="edit.html" method="post"> <input type="hidden" name="productId"      value="<c:out value="${command.productId}"/>"> <table>     <tr>         <td>Name</td>         <td><spring:bind path="command.name">                 <input name="name" value="<c:out value="${status.value}"/>">                 <span ><c:out value="${status.errorMessage}"/></span>             </spring:bind>         </td>     </tr>     <tr>         <td>Expiration Date</td>         <td><spring:bind path="command.expirationDate">                 <input name="expirationDate"                      value="<c:out value="${status.value}"/>">                 <span ><c:out value="${status.errorMessage}"/></span>             </spring:bind>         </td>     </tr>     <tr>         <td></td>         <td><input type="submit"></td>     </tr> </table> </form> </body> </html>
image from book

The spring:bind tag Spring provides allows us to pass the values from the form in a very simple way and also provides simple validation. First, we bind a value to a path, which is the command object name (as set in the form controller; the default value is command) and the property we set in the field. Inside the spring:bind tag, Spring defines the object status, whose value field represents the value of the property defined in the spring:bind tag. The status.errorMessage defines any validation error message.

The last thing we need to do is modify the application context file, the productFormController bean, and a mapping from /product/edit.html to the form controller. This is shown in Listing 17-25.

Listing 17-25: ProductFormController Definition and URL Mapping

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean           >         <property name="mappings">             <props>                 <prop key="/index.html">indexController</prop>                 <prop key="/product/index.html">productController</prop>                 <prop key="/product/view.html">productController</prop>                 <prop key="/product/edit.html">productFormController</prop>             </props>         </property>     </bean>          <!-- Product -->     <bean bold">productFormController"          bold">com.apress.prospring.ch17.web.product.ProductFormController">     </bean>     <!-- other beans as usual --> </beans>
image from book

As you can see, there is nothing unusual about the new definitions in the Spring application context file. If we now navigate to http://localhost:8080/ch17/product/edit.html, we see a typical web page with a form to enter the data (see Figure 17-8).

image from book
Figure 17-8: Product edit form

Unfortunately, the expirationDate property is of type Date, and Java date formats are a bit difficult to use. You cannot expect users to type Sun Oct 24 19:20:00 BST 2004 for Date values. To make things a bit easier for the users, in Listing 17-26, we register a custom editor with the current binder. To do this, we are going to override the initBinder() method.

Listing 17-26: CustomEditor Registration in ProductFormController

image from book
package com.apress.prospring.ch17.web.product;      public class ProductFormController extends SimpleFormController {          // other methods omitted for clarity          protected void initBinder(HttpServletRequest request,          ServletRequestDataBinder binder) throws Exception {         SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");         dateFormat.setLenient(false);         binder.registerCustomEditor(Date.class, null,              new CustomDateEditor(dateFormat, false));     } } 
image from book

The newly registered custom editor is applied to all Date.class values and the values are parsed as dd/MM/yyyy values, as a result, "24/10/2004" is accepted instead of "Sun Oct 24 19:20:00 BST 2004" as a valid Date value. There is one other important thing missing: validation. We do not want to allow the users to add a product with no name. The most elegant way to implement validation is to implement the Validator interface, as shown in Listing 17-27, register the ProductValidator bean as a Spring-managed bean, and set the ProductFormController's validator property to the productValidator bean.

Listing 17-27: ProductValidator Bean Implementation

image from book
package com.apress.prospring.ch17.business.validators;      import org.springframework.validation.Errors; import org.springframework.validation.Validator;      import com.apress.prospring.ch17.domain.Product;      public class ProductValidator implements Validator {          public boolean supports(Class clazz) {         return clazz.isAssignableFrom(Product.class);     }          public void validate(Object obj, Errors errors) {         Product product = (Product)obj;         if (product.getName() == null || product.getName().length() == 0) {              errors.rejectValue("name", "required", "");         }     }      }
image from book

This Validator implementation adds a validation error with errorCode set to required. This code identifies a message resource, which needs to be resolved using a messageSource bean. The messageSource bean allows the message strings to be externalized and supports internalization as well. The rules for creating internalized messages are exactly the same as the rules for creating internationalized views and themes. We only show the final application context file in Listing 17-28; we do not show the contents of the messages.properties and messages_CS.properties files.

Listing 17-28: ProductFormController Definition and URL Mapping

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean bold">messageSource"          bold">ResourceBundleMessageSource">         <property name="basename"><value>messages</value></property>     </bean>          <bean bold">productValidator"          bold">ProductValidator"/>          <!-- Product -->     <bean bold">productFormController"          bold">com.apress.prospring.ch17.web.product.ProductFormController">         <property name="validator"><ref bean="productValidator"/></property>     </bean>     <!-- other beans as usual --> </beans>
image from book

When we rebuild and redeploy the application, go to the product/edit.html page, and try to submit the form without filling in the name but provide a valid expiration date, we see an error message in the appropriate language, as shown in Figure 17-9.

image from book
Figure 17-9: Edit page with validation errors

You now know how to get new data from the users, but in a typical application, you have to deal with edits as well. There must be a way to prepare the command object so it contains data retrieved from the Business Tier. Typically, this means that the request to the edit page contains a request parameter that specifies the object's identity. The object is then loaded in a call to the Business Tier and presented to the user. To do this, override the formBackingObject() method, as shown in Listing 17-29.

Listing 17-29: Overriding the formBackingObject() Method

image from book
package com.apress.prospring.ch17.web.product;      public class ProductFormController extends SimpleFormController {          // other methods omitted for clarity          protected Object formBackingObject(HttpServletRequest request)          throws Exception {         Product command = new Product();         int productId = RequestUtils.getIntParameter(request, "productId", 0);         if (productId != 0) {             // load the product             command.setProductId(productId);             command.setName("loaded");         }                  return command;     } }
image from book

And behold: when we make a request to edit.html with request parameter productId set to 2, the command object's name property is set to loaded. Of course, instead of creating an instance of the Product object in the controller, we use a Business Tier to pass the object identified by productId.

The other controllers follow the same rules for processing form submission and validation, so you do not need to describe them any further. The Spring sample applications explain the uses of other controllers.

Exploring the AbstractWizardFormController

A very useful subclass of AbstractFormController is AbstractWizardFormController, which allows you to implement a wizard-like series of pages. To demonstrate how to use this Controller implementation, we begin with a simple set of JSP pages: step1.jsp, step2.jsp, and finish.jsp (see Listing 17-30). The code of these JSP pages is not too different from the code used in the edit.jsp page used in Listing 17-24.

Listing 17-30: Code for the step1.jsp, step2.jsp, and finish.jsp Pages

image from book
 // step1.jsp <%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%> <%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>      <html> <head>     <c:set var="css"><spring:theme code="css"/></c:set>        <c:if test="${not empty css}"><link rel="stylesheet"              href="<c:url value="${css}"/>" type="text/css" /></c:if> </head> <body> <form action="wizard.html?_target1" method="post"> <input type="hidden" name="_page" value="0"> <table>     <tr>         <td>Name</td>         <td><spring:bind path="command.name">                 <input name="name" value="<c:out value="${status.value}"/>">                 <span ><c:out value="${status.errorMessage}"/></span>             </spring:bind>         </td>     </tr>     <tr>         <td></td>         <td><input type="submit" value="Next"></td>     </tr> </table> </form> </body> </html>      // step2.jsp <%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%> <%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>      <html> <head>     <c:set var="css"><spring:theme code="css"/></c:set>        <c:if test="${not empty css}"><link rel="stylesheet"              href="<c:url value="${css}"/>" type="text/css" /></c:if> </head> <body> <form action="wizard.html?_target2" method="post"> <input type="hidden" name="_page" value="1">      <table>     <tr>         <td>Expiration Date</td>         <td><spring:bind path="command.expirationDate">                 <input name="expirationDate"                        value="<c:out value="${status.value}"/>">                 <span ><c:out value="${status.errorMessage}"/></span>             </spring:bind>         </td>     </tr>     <tr>         <td></td>         <td><input type="submit" value="Next"></td>     </tr> </table> </form> </body> </html>      // finish.jsp <%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%> <%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>      <html> <head>     <c:set var="css"><spring:theme code="css"/></c:set>        <c:if test="${not empty css}"><link rel="stylesheet"              href="<c:url value="${css}"/>" type="text/css" /></c:if> </head> <body> <form action="wizard.html?_finish" method="post"> <input type="hidden" name="_page" value="2"> <table>     <tr>         <td>Register now?</td>         <td><c:out value="${command}"/></td>     </tr>     <tr>         <td></td>         <td><input type="submit" value="Next"></td>     </tr> </table> </form> </body> </html>
image from book

As you can see, the step1.jsp and step2.jsp pages simply populate the name and expirationDate properties of the command object, which is an instance of the Product domain object.

Now that we have set up the JSP pages, we must take a look at how the AbstractWizardFormController uses request parameters to control the page flow of the wizard. These parameters are summarized in Table 17-7.

Table 17-7: Page Flow Request Parameters

Parameter

Description

_target<value>

The value is a number that specifies the index in the pages[] property that the controller should go to when the current page is submitted and is valid or when the allowDirtyForward or allowDirtyBack properties are set to true.

_finish

If this parameter is specified, the AbstractWizardFormController invokes the processFinish() method and removes the command object from the session.

_cancel

If this parameter is specified, the AbstractWizardFormController invokes the processCancel() method, which, if not overridden, just removes the command object from the session. If you choose to override this method, do not forget to call the super() method or remove the command object from the session manually.

_page

This parameter (usually specified as <input type="hidden" name="_page" value="">) specifies the index of the page in the pages[] property.

Now that we have the JSP pages that form the wizard steps, we need to implement the RegistrationController as a subclass of the AbstractWizardFormController, as shown in Listing 17-31.

Listing 17-31: RegistrationController Implementation

image from book
package com.apress.prospring.ch17.web.registration;      public class RegistrationController extends AbstractWizardFormController {          public RegistrationController() {         setPages(new String[] {"registration-step1", "registration-step2",              "registration-finish"});         setSessionForm(true);         setCommandClass(Product.class);     }          protected ModelAndView processFinish(HttpServletRequest request,          HttpServletResponse response, Object command,          BindException errors) throws Exception {         Product product = (Product)command;                  System.out.println("Register " + product);         return null;     }          protected void initBinder(HttpServletRequest request,          ServletRequestDataBinder binder) throws Exception {         SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");         dateFormat.setLenient(false);         binder.registerCustomEditor(Date.class, null,              new CustomDateEditor(dateFormat, false));     }          protected void validatePage(Object command, Errors errors, int page,          boolean finish) {         getValidator().validate(command, errors);     } }
image from book

The code shown in Listing 17-31 represents almost the simplest implementation of the AbstractWizardFormController subclass. Technically, all we have to implement is the processFinish() method; but in our case, we also need to register a custom editor for the Date class. Finally, we want to set the commandClass property to Product.class. We can set the pages and sessionForm properties in the bean definition, which is shown in Listing 17-32, but we decided to set the properties in the constructor instead.

Listing 17-32: RegistrationController Bean and URL Mappings

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"      "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean           >         <property name="interceptors">             <list>                 <ref local="bigBrotherHandlerInterceptor"/>             </list>         </property>         <property name="mappings">             <props>                 <!-- other props omitted -->                 <prop key="/registration/wizard.html">registrationController</prop>             </props>         </property>     </bean>     <bean bold">registrationController"          >         <property name="validator"><ref bean="productValidator"/></property>         </bean> </beans> 
image from book

Notice that we have not created mappings for the step1.jsp, step2.jsp, and finish.jsp pages; instead we have only created a single mapping for /registration/wizard.html, which is handled by the registrationController bean. We also set the validator property of the registrationController to the productValidator bean. We use the validator property in the validatePage() method to show that we can validate each page. The implementation we choose is exactly the same as the default implementation in AbstractWizardFormController, but if we want to, we can allow the user to move to the next page. The AbstractWizardFormController performs the validation before calling the processFinish() method, so there is no way to avoid validation and it is safe to skip validation on certain pages—the command object in the processFinish() method is guaranteed to be valid.[2]

The explanation of the AbstractWizardFormController we offer here is quite simple, but even so, it should give you a good starting point if you decide to use AbstractWizardFormController subclasses in your application.

File Upload

Spring handles file upload through implementations of the MultipartResolver interface. Out of the box, Spring comes with support for COS FileUpload and Commons FileUpload. By default, no default multipartResolver bean is declared, so if you want to use either the Commons or COS implementations or provide your own implementation, you have to declare the multipartResolver bean in the Spring application context, as shown in Listing 17-33.

Listing 17-33: multipartResolver Declaration for Commons and COS FileUpload

image from book
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean           symbol">¿             commons.CommonsMultipartResolver">         <property name="maxUploadSize"> <value>100000</value> </property>      </bean>          <bean          >         <property name="maxUploadSize"><value>100000</value></property>     </bean>     <!-- other beans as usual --> </beans> 
image from book

Do not forget that you can only have one multipartResolver bean; this means you have to choose which one to use when you declare the beans. Once the multipartResolver bean is configured, Spring knows how to handle multipart/form-data encoded requests to transform the form data into a byte[] array. To demonstrate that our newly configured multipartResolver works, we are going to create ProductImageForm (see Listing 17-34) and ProductImageFormController (see Listing 17-35) classes. The first one extends SimpleFormController and handles the image upload, while the second one contains properties for the image name and contents.

Listing 17-34: ProductImageForm Implementation

image from book
package com.apress.prospring.ch17.web.product;      public class ProductImageForm {          private String name;     private byte[] contents;          public byte[] getContents() {         return contents;     }          public void setContents(byte[] contents) {         this.contents = contents;     }          public String getName() {         return name;     }          public void setName(String name) {         this.name = name;     } }
image from book

There is nothing spectacular about this class; it is a simple Java bean that exposes the name and contents properties.

The ProductImageFormController's initBinder() method is much more interesting (see Listing 17-35).

Listing 17-35: ProductImageFormController Implementation

image from book
package com.apress.prospring.ch17.web.product;      import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;      import org.springframework.validation.BindException; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.SimpleFormController;      public class ProductImageFormController extends SimpleFormController {          public ProductImageFormController() {         super();         setCommandClass(ProductImageForm.class);         setFormView("products-image");     }          protected ModelAndView onSubmit(HttpServletRequest request,          HttpServletResponse response, Object command,          BindException errors) throws Exception {         ProductImageForm form = (ProductImageForm)command;                  System.out.println(form.getName());         byte[] contents = form.getContents();         for (int i = 0; i < contents.length; i++) {             System.out.print(contents[i]);         }              return new ModelAndView("products-index-r");     }          protected void initBinder(HttpServletRequest request,          ServletRequestDataBinder binder) throws Exception {         binder.registerCustomEditor(byte[].class,              new ByteArrayMultipartFileEditor());     }      }
image from book

The ByteArrayMultipartResolver class uses the multipartResolver bean from the application context to parse the contents of the multipart stream and return it as a byte[]array, which is then processed in the onSubmit() method.

Be careful when coding the JSP page for the file upload. Our favorite error is to forget the enctype attribute (highlighted in Listing 17-36) of the form element.

Listing 17-36: Image.jsp Form

image from book
<%@taglib prefix="c" uri="http://java.sun.com/jstl/core"%> <%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>      <html> <head>     <c:set var="css"><spring:theme code="css"/></c:set>        <c:if test="${not empty css}"><link rel="stylesheet"              href="<c:url value="${css}"/>" type="text/css" /></c:if> </head>      <body> <form action="image.html" method="post" enctype="multipart/form-data"> <table>     <tr>         <td>Name</td>         <td><spring:bind path="command.name">                 <input name="name" value="<c:out value="${status.value}"/>">                 <span ><c:out value="${status.errorMessage}"/></span>             </spring:bind>         </td>     </tr>     <tr>         <td>Image</td>         <td><spring:bind path="command.contents">                 <input name="contents" type="file">                 <span ><c:out value="${status.errorMessage}"/></span>             </spring:bind>         </td>     </tr>     <tr>         <td></td>         <td><input type="submit"></td>     </tr> </table> </form> </body> </html>
image from book

As you can see, the JSP page is a standard HTML page, except for the enctype attribute. We must not forget to define this JSP page as a view in the views.properties file. Once we have done all that and recompiled and redeployed the application, we should be able to use the file upload page at products/image.html (see Figure 17-10).

image from book
Figure 17-10: File upload test

File upload—or multipart handling in general—concludes the discussion of Spring MVC.

[2] The command object is valid only if we supply an appropriate Validator implementation.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net