3. Code Example

The person controller still handles delete and search. It also has a newPerson() and newAddress() method used by the 'create' action-state in the person and address flows to put a new instance in scope for the form to bind to. This method could be called on any bean though. It doesn't need to be on the person controller.

Example 1. PersonController

                
@Controller
public class PersonController {

    final Logger logger = LoggerFactory.getLogger(PersonController.class);
    
    static final String SEARCH_VIEW_KEY = "/person/search";
    static final String SEARCH_MODEL_KEY = "persons";

    @Autowired
    protected PersonDao personDao = null;

    /**
     * Deletes a person.
     */
    @RequestMapping(value="/person/delete.html")
    public ModelAndView delete(@RequestParam("id") Integer id) {
        Person person = new Person();
        person.setId(id);
        
        personDao.delete(person);

        return new ModelAndView(SEARCH_VIEW_KEY, SEARCH_MODEL_KEY, search());
    }

    /**
     * Searches for all persons and returns them in a 
     * <code>Collection</code> as 'persons' in the 
     * <code>ModelMap</code>.
     */
    @RequestMapping(value="/person/search.html")
    @ModelAttribute(SEARCH_MODEL_KEY)
    public Collection<Person> search() {
        Collection<Person> lResults = personDao.findPersons();
        
        return lResults;
    }

}
                
            

At the end of the flow and when exception occurs that the flow doesn't handle, the PersonFlowHandler redirects to the search page.

Example 2. PersonFlowHandler

                
@Component
public class PersonFlowHandler extends AbstractFlowHandler {

    /**
     * Where the flow should go when it ends.
     */
    @Override
    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
                                         HttpServletRequest request, HttpServletResponse response) {
        return getContextRelativeUrl(PersonController.SEARCH_VIEW_KEY);
    }

    /**
     * Where to redirect if there is an exception not handled by the flow.
     */
    @Override
    public String handleException(FlowException e, 
                                  HttpServletRequest request, HttpServletResponse response) {
        if (e instanceof NoSuchFlowExecutionException) {
            return getContextRelativeUrl(PersonController.SEARCH_VIEW_KEY);
        } else {
            throw e;
        }
    }
    
    /**
     * Gets context relative url with an '.html' extension.
     */
    private String getContextRelativeUrl(String view) {
        return "contextRelative:" + view + ".html";
    }

}
                
            

Person Validator that is automatically called by Spring Web Flow based on bean name (${model} + 'Validator') and the method based binding in a view-state.

Example 3. PersonValidator

                
@Component
public class PersonValidator {

    /**
     * Spring Web Flow activated validation (validate + ${state}).
     * Validates 'personForm' view state after binding to person.
     */
    public void validatePersonForm(Person person, MessageContext context) {
        if (!StringUtils.hasText(person.getFirstName())) {
            context.addMessage(new MessageBuilder().error().source("firstName").code("person.form.firstName.error").build());
        }

        if (!StringUtils.hasText(person.getLastName())) {
            context.addMessage(new MessageBuilder().error().source("lastName").code("person.form.lastName.error").build());
        }
    }
    
}
                
            

Example 4. Excerpt from Address

                
/**
 * Validates 'addressForm' view state after binding to address.
 * Spring Web Flow activated validation ('validate' + ${state}).
 */
public void validateAddressForm(MessageContext context) {
    if (!StringUtils.hasText(address)) {
        context.addMessage(new MessageBuilder().error().source("address").code("address.form.address.error").build());
    }
    
    if (!StringUtils.hasText(city)) {
        context.addMessage(new MessageBuilder().error().source("city").code("address.form.city.error").build());
    }
    
    if (!StringUtils.hasText(state)) {
        context.addMessage(new MessageBuilder().error().source("state").code("address.form.state.error").build());
    }
    
    if (!StringUtils.hasText(zipPostal)) {
        context.addMessage(new MessageBuilder().error().source("zipPostal").code("address.form.zipPostal.error").build());
    }
    
    if (!StringUtils.hasText(country)) {
        context.addMessage(new MessageBuilder().error().source("country").code("address.form.country.error").build());
    }
}