Forms
DateTimeFormat.notes

@DateTimeFormat


style


S- 05/11/10
M- 05-Nov-2010
L- 05 November 2010
F- Friday, 5 November 2010


-S 09:21
-M 09:22:07
-L 09:22:27 GMT
-F 09:22:50 o'clock GMT


MS 05-Nov-2010 09:24
SL 05/11/10 09:24:30 GMT
LL 05 November 2010 09:24:53 GMT


pattern


MMMM, yyyy November, 2010
yyyy.MM.dd G 'at' HH:mm:ss z 2010.11.05 AD at 09:30:33 GMT
hh 'o''clock' a, zzzz 09 o'clock AM, Greenwich Mean Time
yyyy-MM-dd'T'HH:mm:ss.SSSZ 2010-11-05T09:31:28.374+0000


iso


NONE 05/11/10 09:39
DATE 2010-11-05
TIME 09:40:28.470Z
DATE_TIME 2010-11-05T09:41:01.096Z
NumberFormat.notes

@NumberFormat


style


CURRENCY £12,345.67
NUMBER 12,345.67
PERCENT 1,234,567%


pattern


###,##0 12,346
###,##0.00;(###,##0.00) 12,345.67
using-lishys-notes.notes

Guide


The guide pane on the right contains information organized into sections.

Use the and buttons to move through the sections, or click an accordion tab (shown below) to go directly to a particular topic.



Code Reference


Each paragraph in the guide is usually associated with a particular piece of code. An orange arrow indicates that the code displayed in the middle pane is relevant to the guide text.



If the arrow is gray, simply click anywhere on the paragraph and the relevant code will be displayed.



Code Highlighter


The highlight feature identifies areas of the code which relate to a word or term in the guide. Hover the mouse over any underlined word and the relevant code will be highlighted.



You can disable the highlighting feature at any time by clicking on the button.

Don't forget to make the appropriate code page visible by clicking on any paragraph with a gray arrow.
MyService.java
package lishy.forms.service;

import lishy.forms.MyDomainObject;

public interface MyService {
    
    public void someBusinessLogic(MyDomainObject obj);

}

MyServiceImpl.java
package lishy.forms.service;

import static java.lang.System.out;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import lishy.forms.MyDomainObject;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService{

    @Autowired
    private Validator validator;

    @Override
    public void someBusinessLogic(MyDomainObject obj) {

        Set<ConstraintViolation<MyDomainObject>> violations =  validator.validate(obj);
        
        System.out.println("Violation count: " + violations.size() + "\n");
        for (ConstraintViolation<MyDomainObject> violation : violations) {
            out.println(violation.getPropertyPath() + ": " + violation.getMessage());
        }

    }

}

MyFormController.java
package lishy.forms.web;

import java.math.BigDecimal;
import java.util.Date;

import javax.validation.Valid;

import lishy.forms.MyDomainObject;
import lishy.forms.MyDomainValidator;

import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

@Controller
@RequestMapping("/my-form")
@SessionAttributes("myDomainObject")
public class MyFormController {
    
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setDisallowedFields("id");
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));  
        binder.setRequiredFields("dateInThePast", "emailAddress");
    }
    
    @RequestMapping(method=RequestMethod.GET)
    public MyDomainObject setUpForm(@RequestParam(value="id", required=false) Integer id) {
        return id==null ? new MyDomainObject() : 
                          new MyDomainObject(id, "abc", new BigDecimal(12345.67), 
                                             new Date(), "XYZ", "x@y.com");
    }

    @RequestMapping(params="save", method=RequestMethod.POST)
    public String save(@Valid MyDomainObject domain, 
                       BindingResult result,
                       SessionStatus status,
                       Model model) {    
        
        MyDomainValidator validator = new MyDomainValidator();
        validator.validate(domain, result);
        if (result.hasErrors()) {
            validator.showBindingResults(result);
            return "my-form";
        }
        
        status.setComplete();
        model.addAttribute("status", "success!");
        return "my-details";
    }

}
MyDomainObject.java
package lishy.forms;

import java.math.BigDecimal;
import java.util.Date;

import javax.validation.constraints.Min;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.annotation.NumberFormat.Style;

public class MyDomainObject {

    private Integer id;
    
    @NotBlank(message="must be entered")
    private String mandatoryValue;
    
    @Min(50)
    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal minimumValue;
    
    @Past    
    @DateTimeFormat(style="SM")
    private Date dateInThePast;
    
    @Pattern(regexp="x.*", 
             flags=Pattern.Flag.CASE_INSENSITIVE,
             message="must start with x or X")
    private String regExp;
    
    @Email
    private String emailAddress;
    
    public MyDomainObject () {}
    
    public MyDomainObject(Integer id, 
                          String mandatoryValue, 
                          BigDecimal minimumValue,
                          Date dateInThePast,
                          String regExp, 
                          String emailAddress) {
        setId(id);
        setMandatoryValue(mandatoryValue);
        setMinimumValue(minimumValue);
        setDateInThePast(dateInThePast);
        setRegExp(regExp);
        setEmailAddress(emailAddress);
    }
    
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getMandatoryValue() {
        return mandatoryValue;
    }
    public void setMandatoryValue(String mandatoryValue) {
        this.mandatoryValue = mandatoryValue;
    }
    public BigDecimal getMinimumValue() {
        return minimumValue;
    }
    public void setMinimumValue(BigDecimal minimumValue) {
        this.minimumValue = minimumValue;
    }
    public Date getDateInThePast() {
        return dateInThePast;
    }
    public void setDateInThePast(Date dateInThePast) {
        this.dateInThePast = dateInThePast;
    }
    public String getRegExp() {
        return regExp;
    }
    public void setRegExp(String regExp) {
        this.regExp = regExp;
    }
    public String getEmailAddress() {
        return emailAddress;
    }
    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

}
MyDomainValidator.java
package lishy.forms;

import static java.lang.System.out;

import java.util.List;

import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;

public class MyDomainValidator {

    public void validate(MyDomainObject domain, Errors errors) {
        if (domain.getMandatoryValue().equals(domain.getRegExp())) {
            errors.reject("validation.global", "global error");
        }
    }
    
    public void showBindingResults(BindingResult result) {
        out.println("Global errors: " + result.getGlobalErrorCount());
        List<ObjectError> global = result.getGlobalErrors();
        for (ObjectError err : global) {
            out.println("  " + err.getObjectName() + ", " + err.getCode());
        }
        
        out.println("Field errors: " + result.getFieldErrorCount());
        List<FieldError> field = result.getFieldErrors();
        for (FieldError err : field) {
            out.println("  '" + err.getRejectedValue() + 
                        "' in " + err.getField() +
                        ", " + err.getCode() +
                        " (" + (err.isBindingFailure() ? "binding)" : 
                                                         "validation)"));
        }
        
        out.println("Disallowed fields targeted: " + 
                    result.getSuppressedFields().length);
        String[] suppressed = result.getSuppressedFields();
        for (int i = 0; i < suppressed.length; i++) {
            out.println("  " + suppressed[i]);
        }
    }

}

validation.css
.error {
  color:  red;
}
my-details.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:url var="myForm" value="/app/my-form.html">

<html>
  <body>
    <h1>Forms</h1>
    <p>
      <a href="${myForm}">create</a>
    </p>
    <p>
      <a href="${myForm}?id=321">update</a>    
    </p>
    <p style="color:green">${status}</p>
  </body>
</html>

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

<c:url var="myForm" value="/app/my-form.html">
<c:url var="myDetails" value="/app/my-details.html">

<html>
  <head>
    <link rel="stylesheet" href="<c:url value=" /css/validation.css">" type="text/css"/>
  </head>
  <body>
    <h1>Forms</h1>
    <a href="${myDetails}">cancel</a>
    
    <form:form modelAttribute="myDomainObject" action="${myForm}" method="post">
    
      <form:errors cssClass="error" element="p">
      
      <c:if test="${not empty myDomainObject.id}">
        <p>Identifier: ${myDomainObject.id}</p>
      </c:if>   
      <p>
        Mandatory: 
        <form:input path="mandatoryValue"> 
        <form:errors path="mandatoryValue" cssClass="error">
      </p>
      <p>
        Minimum: 
        <form:input path="minimumValue"> 
        <form:errors path="minimumValue" cssClass="error">
      </p>
      <p>
        Date in the past: 
        <form:input path="dateInThePast"> 
        <form:errors path="dateInThePast" cssClass="error">
      </p>
      <p>
        Regular expression: 
        <form:input path="regExp"> 
        <form:errors path="regExp" cssClass="error">
      </p>
      <p>
        Email address: 
        <form:input path="emailAddress"> 
        <form:errors path="emailAddress" cssClass="error">
      </p>
      
      <form:errors path="*" cssClass="error" element="p">
      
      <button type="submit" name="save">
        ${(empty myDomainObject.id) ? 'create' : 'update'}
      </button> ${status}
      
    </form:form>
    
  </body>
</html>

messages.properties
validation.global=Global validation (not related to a single field)

typeMismatch.myDomainObject.minimumValue=minimum value must be a valid number
typeMismatch.dateInThePast=this date must be valid
typeMismatch.int={0} must be a valid number
typeMismatch.java.util.Date={0} must be a valid date
typeMismatch={0} is not valid

required.myDomainObject.minimumValue=minimum value is required
required.dateInThePast=past date must be entered
required.int=you must specify a value for {0}
required.java.util.Date=this date must be entered
required={0} is mandatory
forms-servlet.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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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
           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
 
  <mvc:annotation-driven/>

  <context:component-scan base-package="lishy.forms"/>
  
  <mvc:view-controller path="/my-details.html" view-name="my-details"/>  

  <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:prefix="/WEB-INF/jsp/"
        p:suffix=".jsp"/>
        
  <bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
        p:basename="/WEB-INF/messages/messages"/>        

</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="2.4"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <servlet>
    <servlet-name>forms</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>forms</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>app/my-details.html</welcome-file>
  </welcome-file-list>

</web-app>
console.log
INFO: Global errors: 1
INFO:   myDomainObject, validation.global
INFO: Field errors: 4
INFO:   'abc' in minimumValue, typeMismatch (binding)
INFO:   'xyz' in emailAddress, Email (validation)
INFO:   '' in mandatoryValue, NotBlank (validation)
INFO:   '' in regExp, Pattern (validation)
INFO: Disallowed fields targeted: 1
INFO:   id

Overview

The setUpForm method responds to a GET request by adding a JavaBeanMyDomainObject:eq(1) to the model, which represents the form backing object.

Here we return an empty objectMyDomainObject:eq(2) or a populated objectMyDomainObject:eq(3) depending on the idid:eq(7) argument, to simulate a create or update operation.

The JSP page uses Spring's formform:form and inputform:input elements to display data from this backing object, onto the form.

We can see that the form backing object""myDomainObject"" is referenced by the formform:form element in modelAttribute, and the fields of this object are accessed by the inputform:input elements in the path attributes.

In fact, formform:form puts the model attribute""myDomainObject"" object in the PageContext so that it can be accessed by the inner tags.

When the form is submitted, data from the form is validated and bound to a commandMyDomainObject:eq(4) object in the method receiving the postPOST.

Details of this data binding process, including any errors encountered, are stored in a BindingResultBindingResult:eq(1) object.
We carry out additional, manual validationvalidate and return to the formmy-form:eq(1) or continue to another pagemy-details, depending on the overall results.

Data Binding

Data binding allows user input to be dynamically bound to the domain model of an application.
Binding rules can be defined on a WebDataBinderWebDataBinder:eq(1) which has been injected into an @InitBinder annotated method.

setDisallowedFields or setAllowedFields avoids a potential security issue by preventing attempts to update fields or properties that do not exist in the form.

Using these (or default) rules, Spring will automatically bind form data to a command objectMyDomainObject:eq(4), on the method receiving the postPOST.

The results of the bind, including any errors, are stored in a BindingResultBindingResult:eq(1) object which must be positioned directly after the command objectMyDomainObject:eq(4).
By default, a new command objectMyDomainObject:eq(4) is created for each post and will only contain data which was included on the form; it is not the same object as the one returned by setUpForm.
Problems can arise if, for example, a unique identifierid:eq(5) (such as a primary key value) is required to update the data, but has not been included on the form.
Adding an object namemyDomainObject:eq(0) or type to the @SessionAttributes annotation will ensure that the original objectMyDomainObject:eq(1) is re-used and no values are lost.

The setComplete()setComplete method on SessionStatusSessionStatus:eq(1) marks the current handler's session processing as complete, allowing for cleanup of session attributes.

Validation

We annotate our domain model with validation constraints@:not(:contains("Format")).
The @Valid annotation tells Spring to validate the associated objectdomain:eq(0) when the form is submitted, and add any errors to the BindingResultBindingResult:eq(1).

A JSR-303 provider, such as Hibernate Validator, is required for this to work.

Custom validationvalidate can also be carried out on a validatorMyDomainValidator:gt(0), and any additional errors added to the results.
This can be useful if, for example, several fields are used in a validation check, or a database lookup is required to see if a value in a column with a unique key, already exists.

In this example, if an arbitrary condition fails then a global error (ie not associated with an individual field) is registered with an error code of validation.global.
This error code is mapped to a real error message in a resource bundle, allowing for internationalization of messages.

One of the advantages of using declarative validation constraints on the domain model is that validation checks can easily be performed at any layer of the stack.

Here we inject the ValidatorValidator:eq(1) class in order to validatevalidate an object in the service layer. This can be useful if the service interface is to be exposed as a web service, for example.

Also, Hibernate will automatically validate any constraints when an object is inserted into, or updated in the database.

Formatting

Spring supports two formatting annotations:
  • @NumberFormat
  • @DateTimeFormat

@NumberFormat can format numbers using a stylestyle:eq(0) such as NUMBER, CURRENCY or PERCENT, or a custom pattern such as #,##0.00 (see java.text.DecimalFormat for details).
This page contains some examples.

@DateTimeFormat formats a date using a style, pattern or iso element.

stylestyle:eq(1) consists of two characters; the first represents the date and the second, the time. Both of these values can be S (small), M (medium), L (large), F (full) or - (omit).

pattern specifies custom patterns such as 'yyyy.MM.dd HH:mm:ss' (see java.text.SimpleDateFormat for details)

iso can be DATE, TIME, DATE_TIME or NONE.
This page contains some examples.

User input must also conform to these formatting rulesFormat:gt(2).

For example, if the number format is CURRENCY, then £123 will be accepted but 123 will not.

To configure formatting in Spring, simply add <mvc:annotation-driven/>mvc:annotation-driven to the web application context.

Joda Time must be present on the classpath for the @DateTimeFormat annotation to work.

Errors

Errors can arise from:
  • Bean validation constraint violations
  • Invalid number or date/time formats
  • Data binding errors
  • Programmatic validation failures

Spring adds any validation@Valid or data bindingsetRequired errors to a BindingResultBindingResult:eq(1) object, which can also be passed into a handler method.
Additional errors can be added to this object using the various reject methods on the ErrorsErrors:eq(1) class.

Validation errors, such as a missing mandatory field, or type mismatch errors, such as entering "xyz" in an Integer field, may be encountered during data binding.

These types of errors can be associated with an error message using the required and typeMismatch prefixes in the message file.

Messages can be associated with a particular field on a named object, a particular field on any object, on any fields of a specified type, and globally for all failed items.

The combination of data binding and validation errors can be displayed on a JSP page using the errorsform:errors element.

Individual field errors are displayed by specifying the field name in the pathpath:odd attribute while global errorsform:errors:eq(0) are displayed by omitting this attribute altogether.

Some basic wildcard* functionality also allows all, or a subset of messages to be displayed.
Home  |  Getting Started  |  Controllers  |  Views  |  Forms  |  REST  |  Testing  |  Configuration  |  MVC Walkthrough  |   lishblog  |  Email Lishy