3. Code Example

The person controller still handles delete and search.

Example 1. PersonController

                
@Controller
public class PersonController {

    final Logger logger = LoggerFactory.getLogger(getClass());
    
    static final String SEARCH_VIEW_PATH_KEY = "/person/search";
    
    private static final String DELETE_PATH_KEY = "/person/delete";
    
    private static final String SEARCH_VIEW_KEY = "redirect:search.html";
    private static final String SEARCH_MODEL_KEY = "persons";

    private final PersonService service;

    @Autowired
    public PersonController(PersonService service) {
        this.service = service;
    }

    /**
     * <p>Deletes a person.</p>
     * 
     * <p>Expected HTTP POST and request '/person/delete'.</p>
     */
    @RequestMapping(value=DELETE_PATH_KEY, method=RequestMethod.POST)
    public String delete(@RequestParam("id") Integer id) {
        logger.info("'{}'  id={}", DELETE_PATH_KEY, id);
        
        service.delete(id);

        return SEARCH_VIEW_KEY;
    }

    /**
     * <p>Searches for all persons and returns them in a 
     * <code>Collection</code>.</p>
     * 
     * <p>Expected HTTP GET and request '/person/search'.</p>
     */
    @RequestMapping(value=SEARCH_VIEW_PATH_KEY, method=RequestMethod.GET)
    public @ModelAttribute(SEARCH_MODEL_KEY) Collection<Person> search() {
        return service.find();
    }

}
                
            

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_PATH_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_PATH_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());
    }
}