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