|
2.3. Enhancing the Web ApplicationThe code that you've seen so far begins to provide much needed organization with distinct models, views, and controllers. In this section, we'll add validation logic, a resolver, and input forms. These enhancements will add better organization to your code and save you time and effort. Like many MVC frameworks, these classic enhancements make it easier to handle the classic flow of control that you generally get with web applications:
These simple enhancements let you manage the flow of control in a manner that's consistent and organized. It also lets you keep code in a consistent place, and keep coupling to a bare minimum. 2.3.1. How do I do that?We're going to code our enhancements in two distinct steps. First, we'll configure the resolver. To do that, we're going to need to change the configuration in the rentaBikeApp-Servlet.xml by adding the following element (Example 2-13). Example 2-13. rentaBikeApp-Servlet.xml<bean > <property name="viewClass"> <value>org.springframework.web.servlet.view.JstlView</value> </property> <property name="suffix"><value>.jsp</value></property> </bean> You can also add a prefix in addition to a suffixfor instance, if you store your JSPs in a subfolder or alternate path. You'll also change the controller. This controller's purpose is to handle form submission. In the previous lab, this controller used the HttpServletRequest's parameters to access the form values, but now, it will use a POJO (Example 2-14). Example 2-14. SubmitBikeController.javapublic class SubmitBikeController extends SimpleFormController { private RentABike facade; public RentABike getFacade( ) { return facade; } public void setFacade(RentABike facade) { this.facade = facade; } public ModelAndView onSubmit(Object command) throws ServletException{ Bike bike = (Bike)command; facade.saveBike(bike); return new ModelAndView(new RedirectView(getSuccessView( )), "rentaBike", facade); } protected Object formBackingObject(HttpServletRequest request) throws Exception { Bike bike = new Bike( ); if(request.getParameter("bikeSerialNo") != null) bike = facade.getBike(request.getParameter("bikeSerialNo")); return bike; } } The controller now has an onSubmit method instead of handleRequest. onSubmit takes the POJO that holds the form field values instead of a raw HttpServletRequest. The other method, formBackingObject, allows you to initialize the POJO that will be bound to the form fields on the first request to the form. You also need to code an input form. The form in Example 2-15 is going to make use of some new Spring-specific tags imported from the Spring taglib, which ships with the rest of the framework. Example 2-15. editBike.jsp<%@ page import="com.springbook.*"%> <%@ include file="include.jsp" %> <%@ taglib prefix="spring" uri="/spring" %> <html> <head> <title> Edit Bike </title> </head> <body> <h1>Edit Bike</h1> <form method="POST"> <spring:hasBindErrors name="bike"> <b>Please fix all errors!</b> </spring:hasBindErrors> <table border="1" cellspacing="2" cellpadding="2"> <tr> <td align="right">Manufacturer:</td> <td> <spring:bind path="bike.manufacturer"> <input type="text" name="manufacturer" value="<c:out value="${status.value}"/>"> <font color="red"><c:out value="${status.errorMessage}"/></font> </spring:bind> </td> </tr> <tr> <td align="right">Model:</td> <td> <spring:bind path="bike.model"> <input type="text" name="model" value="<c:out value="${status.value}"/>"> <font color="red"><c:out value="${status.errorMessage}"/></font> </spring:bind> </td> </tr> <tr> <td align="right">Frame:</td> <td> <spring:bind path="bike.frame"> <input type="text" name="frame" value="<c:out value="${status.value}"/>"> <font color="red"><c:out value="${status.errorMessage}"/></font> </spring:bind> </td> </tr> <tr> <td align="right">Serial Number:</td> <td> <spring:bind path="bike.serialNo"> <input type="text" name="serialNo" value="<c:out value="${status.value}"/>"> <font color="red"><c:out value="${status.errorMessage}"/></font> </spring:bind> </td> </tr> <tr> <td align="right">Weight:</td> <td> <spring:bind path="bike.weight"> <input type="text" name="weight" value="<c:out value="${status.value}"/>"> <font color="red"><c:out value="${status.errorMessage}"/></font> </spring:bind> </td> </tr> <tr> <td align="right">Status:</td> <td> <spring:bind path="bike.status"> <input type="text" name="status" value="<c:out value="${status.value}"/>"> <font color="red"><c:out value="${status.errorMessage}"/></font> </spring:bind> </td> </tr> </table> <input type="submit" value="Submit"> </form> </body> </html> See how you surround the form input fields with the <spring:bind> tags. This allows Spring to automap the values of the input fields to our POJO command object, and later, allows it to show error messages if the validation fails. At the top of the page, you can use the <spring:hasBindErrors> tag (passing in our command object) to display an error summary if there are validation errors. You'll want Spring to validate the form, so add a specific validator (Example 2-16). You can package it with a business object. The controller will apply the correct work flow, including calling the validation. Example 2-16. BikeValidator.javapublic class BikeValidator implements Validator { public boolean supports(Class aClass) { return aClass.equals(Bike.class); } public void validate(Object o, Errors errors) { Bike bike = (Bike)o; if(bike == null) { errors.rejectValue("manufacturer", "Error!", null, "Value required."); } else { if(bike.getManufacturer( ) == null || "".equals(bike.getManufacturer( ))) errors.rejectValue("manufacturer", "Value not present.", null, "Manufacturer required."); if(bike.getModel( ) == null || "".equals(bike.getModel( ))) errors.rejectValue("model", "Value not present.", null, "Model is required."); } } } Next, modify the context as in (Example 2-17); also, in the urlMapping, point /editBike.bikes to editBikeForm instead of editBikeController. Example 2-17. rentaBikeApp-Servlet.xml, editBike.bikes should point to editBikeForm<bean /> <bean > <property name="sessionForm"><value>true</value></property> <property name="commandName"><value>bike</value></property> <property name="commandClass"> <value>com.springbook.Bike</value> </property> <property name="validator"><ref bean="bikeValidator"/></property> <property name="formView"><value>editBike</value></property> <property name="successView"><value>bikes.bikes</value></property> <property name="facade"> <ref bean="rentaBike"/> </property> </bean> Next, you'll need to add the CRUD functionality to ArrayListRentABike that you specified earlier in the RentABike interface. Example 2-18. ArrayListRentABike.javapackage com.springbook; import java.util.*; public class ArrayListRentABike implements RentABike { private String storeName; final List bikes = new ArrayList( ); public void setStoreName(String name) { this.storeName = name; } public String getStoreName( ) { return storeName; } private void initBikes( ) { bikes.add(new Bike("Shimano", "Roadmaster", 20, "11111", 15, "Fair")); bikes.add(new Bike("Cannondale", "F2000 XTR", 18, "22222", 12, "Excellent")); bikes.add(new Bike("Trek", "6000", 19, "33333", 12.4, "Fair")); } public ArrayListRentABike( ) { initBikes( ); } public ArrayListRentABike(String storeName) { this.storeName = storeName; initBikes( ); } public String toString( ) { return "com.springbook.RentABike: " + storeName; } public List getBikes( ) { return bikes; } public Bike getBike(String serialNo) { Iterator iter = bikes.iterator( ); while(iter.hasNext( )) { Bike bike = (Bike)iter.next( ); if(serialNo.equals(bike.getSerialNo( ))) return bike; } return null; } public void saveBike(Bike bike) { deleteIfContains(bike); bikes.add(bike); } public void deleteBike(Bike bike) { deleteIfContains(bike); } private void deleteIfContains(Bike bike) { Iterator iter = bikes.iterator( ); while(iter.hasNext( )) { Bike comp = (Bike)iter.next( ); if(comp.getManufacturer( ).equals(bike.getManufacturer( )) && comp.getModel( ).equals(bike.getModel( ))) { bikes.remove(comp); return; } } } } Finally, you'll need to add the Spring taglib to the web.xml file so that your new JSP tags will work. Example 2-19. web.xml<taglib> <taglib-uri>/spring</taglib-uri> <taglib-location>/WEB-INF/lib/spring.tld</taglib-location> </taglib> Go ahead and build and deploy the changed application. Figure 2-3 shows a view of what happens when you don't enter the Manufacturer or Model for a bike. Figure 2-3. Manufacturer and Model missing2.3.2. What just happened?Within the Spring configuration file, you specified the details for a view resolver. This component decouples the view from the technology that you use to render it. In this case, the resolver lets you refer to a JSP by name, instead of through a filename. In the previous simpler example, the control was straightforward. An HTTP request triggered a controller, which then loaded another form. This application is slightly more advanced, because Spring's forms let you provide a more sophisticated workflow, and one that's more appropriate for basic input form submission and validation. In this case, you set up and configured an input form, with custom JSP tags, and then submitted it. The Post will trigger the dispatcher, as before, and then fire the command, invoke the validator, and then return the form view if errors existed, or the success view if there were no errors. If you're familiar with the Jakarta Struts project, this flow will be familiar to you. |
|