Real-World Examples of Using Controllers


The sample application featured is more extensively described in Chapter 15. This section will show you how we'll be implementing some of the use cases mentioned in Chapter 15. Using a simple controller,we'll allow users to view information about a specific show. Also, we'll create a form controller with which bookings can be made, and last but not least, we'll create a wizard-style process to add shows to our box office.

Viewing a List of Performances Using the AbstractController

Users can select a show from the list of shows the welcome screen presents them with. The show the user selects will then be displayed along with the performance and availability data. Let's implement an AbstractController modeling this behavior.

Important 

When one of the other controllers Spring offers doesn't suit your needs and you're planning to implement the Controller interface directly, consider extending the AbstractController instead. It offers many convenient configuration parameters enabling you to customize the controller’s behavior.

Workflow of the ViewShowController

Let's first review the workflow the controller will be responsible for:

  1. The user will arrive at the welcome screen where he or she will be presented with a list of genres and corresponding shows. Each show is represented by an identifier that is placed in a link on the screen. Clicking on one of those links will trigger our controller.

  2. The controller will inspect what show the user selected by retrieving the identifier from the HttpServletRequest.

  3. Using the identifier, the middle tier will have to retrieve the show corresponding to the identifier.

  4. Our controller will have to determine a logical view name and the contents of the model with which, for example, a JSP can render the view (the show, including data availability).

Step 2 mentions a request. Assume the request to be the following:

 http://www.springframework.org/sample/viewshow.html?id=1234 

Implementing the Controller

First, we'll implement our controller. We'll do this using two possible approaches. The first approach starts with extending the AbstractController. Implementing the template method provided by the superclass is all you need to do.

 import org.springframework.web.servlet.mvc.AbstractController; import org.springframework.sample.boxoffice.logic.BoxOffice; .... public class ViewShowController extends AbstractController {          /** The boxoffice domain object we’ll      private BoxOffice boxOffice;          /** And a setter to be able to wire it up in our application context */     public void setBoxOffice(BoxOffice boxOffice) {         this.boxOffice = boxOffice;     } 

The following template method will retrieve the show the user selected and return an appropriate model:

     protected ModelAndView handleRequestInternal(         HttpServletRequest request,         HttpServletResponse response) {              // first, inspect the request for an identifier         long showId = RequestUtils.getRequiredLongParameter(request, "id");              Show show = boxOffice.getShow(showId);              return new ModelAndView("showdetails").addObject("show", show);     } } 

We're using Spring's RequestUtils convenience class (located in the org.springframework.web.bind package) to retrieve the show identifier. This is a useful class to use in simple cases where advanced data binding is overkill. If the required request parameter isn't present, the getRequiredLongParameter() method will throw an exception, which you should preferably handle by adding a HandlerExceptionResolver to your application context (discussed later in this chapter).

Important 

Note that we're creating and returning the ModelAndView in a single line of code. In general, you should be careful with this because it makes debugging more difficult. If NullPointerExceptions are thrown, it's difficult to see what the cause is. In this case it's obvious, however, that this code won't throw any exceptions.

Important 

Another note about exceptions: In the previous example we're not including any exception in the throws clause of the handleRequestInternal() method. Most methods in Spring Web MVC are allowed to throw arbitrary exceptions. However, as noted in the discussion of exception signatures earlier in this chapter, we recommend using the narrowest throws clause you can, in accordance with good Java style.

We could have also chosen to use the ThrowawayController approach to implementing the functionality. The ThrowawayController offers more of a command-style approach in that for each request, a controller is instantiated and populated with the values from the request, after which the controller should have enough information to proceed and return a ModelAndView. The advantage when using these kinds of controllers is that you won't be tied to the Servlet API because all information from the HttpServletRequest is already transferred to the command object (the controller itself) by the framework and no reference to the request itself is given to the controller. This means testing outside a container without mock objects. For example, the HttpServletRequest is pretty easy.

 import org.springframework.web.servlet.mvc.throwaway.ThrowawayController; import org.springframework.sample.boxoffice.logic.BoxOffice;      public class ViewShowController implements ThrowAwayController {          private Long id;          /** upon execution, the HTTP request parameter 'id’     public void setId() {         this.id = id;     }          /** method to implement, specified by ThrowawayController interface */     public ModelAndView execute() {         if (id != null) {                        Show show = boxOffice.getShow(Long.parseLong(showId));             ModelAndView mav = new ModelAndView("showdetails");             mav.addObject("show", show);             return mav;         } else {             return new ModelAndView("error", "message", "unexpected.noshowid");         }     } } 

Wiring Up the Controller

Now that we've created our controller, we're going to wire it up using our application context located in the WEB-INF directory of our web application.

 <bean name="/viewshow.html"          >     <property name="boxOffice">         <ref bean="boxOffice"/>     </property> </bean> 

This is all you need to do to be able to retrieve shows using your middle tier and allow the view technology of your choice to render an HTML page or perhaps create PDF, Excel, or other content.

Making a Reservation

The application allows users to reserve seats and purchase tickets. From the screen presenting the show and corresponding performances and availability, the user can select a specific performance, reserve a certain number of seats, and purchase the actual tickets. Before we go on, let's have a more detailed look at what workflow is involved with doing the actual purchase:

  1. From the screen presenting the user with the details of the show, he can select a specific performance and a price band. This will take the user to a screen where he can select the number of seats he wants to reserve.

  2. At this moment, the application will present the user with the number of seats available. The user will be taken to a form that will allow him to purchase the tickets.

  3. The form will be presented alongside some details about the performance the user is purchasing tickets for. The form consists of text fields and checkboxes and other widgets with which the user will be able to fill in his personal information (first name, last name, and so on) and payment details (credit card number, type of credit card).

  4. Submitting the form will trigger some additional processing, and after that, validation will be performed. If errors are encountered, the form will be redisplayed, now containing error messages. The user will then be able to fill in the fields that contained errors.

  5. After the validation succeeds, the actual payment will be done and the tickets will be purchased. The user will then be taken to another page telling him the purchase has been completed successfully.

SimpleFormController

In a typical form-editing and -submission process, users are first presented with a form, which they can fill out and submit, after which data binding and validation will be done. If all goes well, the application might, for example, store something in a database after which it tells the user everything went just fine.

The org.springframework.web.servlet.mvc.SimpleFormController does exactly this. It is one of the most useful and often-used controllers Spring provides and can be almost completely configured using Spring's Dependency Injection features.

The process of implementing a simple form controller can be summarized as follows (items between parentheses are not always necessary):

  • Extend org.springframework.web.servlet.mvc.SimpleFormController.

  • Set the formView and successView properties used in your application context.

  • Add a setter for your service object capable of, for example, storing the form object in the database.

  • (Override the referenceData() method to dig up some options to display in a drop-down box or choices as part of a group of checkboxes.)

  • (Override the formBackingObject() method to get rid of Spring's default behavior of instantiating a simple command class or set the commandClass property if you just want an empty command object backing your form.)

  • (Add a couple of validation objects using the validators property.)

  • Implement the doSubmitAction() method to manage a fully processed, validated, and ready- to-store object.

There is, however, a lot more to tell. Before we begin, let's review the properties and some of the call-backs and other customization possibilities involved with the SimpleFormController.

Important 

Don't let the flexibility of the command controllers and form controllers scare you off! The authors usually find themselves overriding only three methods: formBackingObject(), referenceData(), and doSubmitAction(); however, we often come across requirements where the construction of a command object is so expensive that doing it twice is just too much. That’s where the sessionForm property comes into play. You’ll find that working with Spring’s controller infrastructure is easy if you don’t have complex requirements, and extremely powerful otherwise.

Properties to Configure the Controller With

The SimpleFormController inherits a lot of its functionality from the AbstractFormController and the BaseCommandController. To tweak its behavior, the following properties are at your disposal:

  • commandClass: This class will by default be instantiated and used as the object that backs the form, meaning the binding of properties will be done on this object. By default, Spring will just instantiate a new object using the default constructor (if available) of the command class specified. Often you don't want just a new class being created, but instead, you want to load an object from the database, for example. In this case you don't need to set the command class, but override formBackObject() instead. This method is discussed later in this chapter.

  • formView and successView: Logical view names pointing to the view containing the form and the view rendering after the form has successfully been submitted, validated, and processed. The SimpleFormController exposes the success and the form view as properties; if you need custom selection of those views, extend the AbstractFormController instead. One of the typical problems with forms is the after-POST problem. If you have done a POST request and you try to refresh it in the browser, it will typically ask you if you want to resubmit the data. Often you don't want to do this. Spring offers you a way to issue a GET request immediately after a POST request has occurred. This feature involved prefixing the view name with redirect:. This feature is discussed in more detail in the next chapter.

  • sessionForm: Based on whether or not this property is set, Spring will instantiate a new object the first time the form is shown and store it in the user's session. Upon form submission, the form will be retrieved from the session and used to bind the incoming data. Of course, upon form submission, the form object will be removed from the session. By default, this property is set to false. If you leave it like that, each and every time the controller services a request (both requests to show the form as well as form submission requests), a new command object will be looked up. Instantiating a simple command class using the default behavior (in other words, if you haven't overridden formBackingObject()) isn't really costly. If you, however, retrieve an object backing the form from the database, you might consider enabling this property because not doing so might have some impact on performance.

  • bindOnNewForm: Property indicating whether or not to bind request parameters when a request for a new form is being processed. If you set this property to true (the default is false), all parameters available in the HttpServletRequest will be bound to the command object whena new form is shown. This can be extremely handy when you want to pre-populate a form.

  • validators: To be used after databinding has been done. Validators are of type org.springframework.validation.Validator and are explained in more detail in Chapter 3.

  • validateOnBinding: By default this property is true; if you set it to false, validation will not occur.

  • commandName: The last property allows you to tweak the name under which the command object (backing the form) will be put in the model. It defaults to command. Not all view technologies have to access the model directly when using a form controller because, for example, the form macros and custom tags provide convenient ways to render form fields and values, but in certain circumstances you might need to access it. The command name defaults to command.

Callbacks and Template Methods for Custom Behavior

There are several callback methods you can implement to get custom behavior or to extend Spring's default behavior. These include methods called right before and after validation and methods to be implemented when you want to perform custom logic involved with the submitting of a form.

  • Map referenceData(HttpServletRequest request, Object command, BindException errors): This method can be used if your form does not only need the command object, but additional data such as options to be displayed in a drop-down box, choices a user can make using checkboxes, and so on. All elements in the map will be merged in the model and thus be available for the view to render.

  • initBinder(HttpServletRequest request, ServletRequestDataBinder binder): Perhaps the most important method in the controller, except for one of the submit actions you need to override to actually do something with the command object. This method allows you to add custom property editors (see Chapter 2 for more information on those) to the binder. The DataBinder is used in the Spring tags to transform domain property (such as a Date, but also domain-specific property such as a Show's Genre to Strings), which after submitting the form can be transformed back again into the original object. This method gives you complete decoupling of the web-specific form controllers and your domain object and is something Struts mimics, using the ActionForms. Because the form controller is completely stateless, this method is called both while issuing the form view request as well as the submit request.

  • onBind(HttpServletRequest request, Object command, BindException errors) and onBind(HttpServletRequest req, Object command): Use this method in case you want to do custom binding. This method is called right after databinding, but before validation. Imagine you have a form containing three input fields together making up a date (one field for the year, one for the month, and one for the day). Using PropertyEditors, you can't bind the three form fields easily to one domain property. You could use a little JavaScript that (on submitting the form) transfers the three properties to a hidden input field and use a property editor to transform the hidden input parameter to a date instead of the three separate fields. Another (and maybe better) approach would be to override onBind() and do the custom binding here yourself.

  • onBindAndValidate(HttpServletRequest req, Object command, BindException errors): Use this method if you need to do custom validation that cannot be performed by the normal validators you’ve specified with the controller. Usually validators don’t have access to the servlet request, so if you want to do validation that cannot do without the request, this is the place.

  • ModelAndView onSubmit(HttpServletRequest req, HttpServletResponse res, Object command, BindException errors) and their simpler forms onSubmit(Object command, BindException errors) and onSubmit(Object command): These methods all return a ModelAndView object (or null if you don’t want the framework to continue with request handling) and are supposed to be overridden to do custom processing involved with form submission. These methods are called after databinding, validation, and only if after validation, the BindException object did not contain any errors. If you want to redisplay the form (for example when errors arise saving the object), you can use the showForm(HttpServletRequest request, HttpServletResponse response, BindException exception).

  • void doSubmitAction(Object command): This method is the simplest form of the submit method. You don't have to bother to return a ModelAndView object; the SimpleFormController automatically creates one for you using the successView property. This is where you can, for example, store the command object in the database using the persistence technology of your choice.

  • isFormChangeRequest(HttpServletRequest request): This method is called by the controller to identify situations in which it should redisplay the form, without doing any validation. This is especially useful in situations where you do an intermediary submit and want to change the data (and maybe the form) itself, based on a selection a user has done. An example could be a form with two select boxes, one filled with countries and one with cities where the one with the cities needs to be updated after a user selects a country. A little JavaScript could trigger the submit action after selecting a country. referenceData() can be used to add the list of corresponding cities to the model. By default, this method returns false, which tells the controller to proceed with the normal workflow involved with form submission.

  • suppressValidation(HttpServletRequest request): This method by default delegates to the isFormChangeRequest() method. In some situations, however, you might want to suppress validation in certain cases. Returning true from this method will do exactly this.

  • handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response): The default implementation of the controller tries to resubmit the form with a newly created form object (retrieved by formBackingObject()). Override it if you want to add custom behavior, such as not allowing users to submit a form when no object is found in the session. The default behavior, by the way, should also work okay when the Back button is clicked after the form has already been submitted.

Important 

Whenever doing things such as custom binding, for example in the onBind() method, you probably need to access the HttpServletRequest. Your best option to do so is using the org.springframework.web.bind.RequestUtils, offering methods such as getIntParameter(), getBooleanParameter(), and corresponding variants that will throw an exception if the parameter does not exist. Using the HttpServletRequest directly is tedious and you need to do lots of checking that can better be avoided (or moved to a utility class).

Form Workflow

The form workflow is graphically laid out in Figures 12-6 and 12-7. Figure 12-6 explains the workflow involved with requests that need to display a form.

image from book
Figure 12-6

image from book
Figure 12-7

Important 

When a form controller services a request, based upon the outcome of the isFormSubmission() method, it starts the form submission process or the work- flow for showing a new form. By default, all GET requests are associated with new forms; all POST requests are treated as form submission requests. You can always modify the behavior by overriding the isFormSubmission(HttpServletRequest) method.

  1. The form display process starts with the creation of a new command. The method called to do this is formBackingObject(). By default, it instantiates a new instance of the command class you have configured. If you override this method, you might instead retrieve an object from the database. For example, include an additional parameter in the request that identifies a certain object you want to edit. Retrieve this parameter in the formBackingObject() method and load the corresponding object using a service object or DAO.

  2. The binder is created and you're allowed to further customize it (add additional property editors, and so on) in the initBinder() method.

  3. The form displaying process continues by determining whether or not binding needs to be done. Binding when displaying a new form can be turned on by setting the bindOnNewForm property to true.

  4. The next thing is to store the object in the session (but only if sessionForm is set to true).

  5. The last thing the controller does is create the model. It actually does so by calling BindException.getModel() or Errors.getModel(). The BindException wraps the actual command object and can create an appropriate model for you. You will probably do this yourself as well once in a while, as we'll see while reviewing the submit process.

The process of submitting a form (laid out in Figure 12-7) is slightly more complicated. It involves checking for the command object, invocation of the callbacks, validation (if needed), and the actual calling of the submit methods.

 import org.springframework.web.servlet.mvc.SimpleFormController;      public ConfirmBookingController extends SimpleFormController {          public void initBinder(         HttpServletRequest request, ServletRequestDataBinder binder)     throws Exception {              binder.registerCustomEditor(new ShowEditor());     } 

As you can see, the binder now knows how to convert shows to String and String back to show — all because we've registered the ShowEditor.

  1. When a form is submitted (usually this is the case when a POST request is done to the controller), the controller first determines whether or not sessionForm is set to true. If so, it retrieves the command object from the session. If none is found, it will call handleInvalidSubmit(), which by default resubmits the form. Overriding is of course possible.

  2. If sessionForm was set to false, the controller will call formBackingObject() again to retrieve a command object.

  3. After the binder has been created (see the preceding code example), binding and validation will be done and the corresponding callbacks will be called to allow for custom processing. Validation, by the way, can be turned off by setting suppressValidation to true.

  4. After the binding has been done and validation succeeded, the onSubmit() callbacks will be called. By default, if you override none of them, the last method called is doSubmitAction(), which returns void and uses the successView configured with the controller.

When validation fails, the process is, of course, a bit different because we have to show the form again, including validation errors. The process somewhat resembles the displaying of the form for the first time. As you know, all Spring controllers result in a ModelAndView being created and so do the form controllers. The model a form controller returns consists of three things:

  • The command object (under the key configured using the commandName property or "command" if you don't configure it)

  • The errors (if any) wrapped by a special utility object

  • The referenced data returned from the referenceData() method, which is merged into the model.

The fact that Spring maintains binding errors separately from the form object means that domain objects can be used for form objects — a contrast with Struts, which requires the use of ActionForm subclasses to hold both form information and any accompanying errors.

When overriding one of the callbacks such as onSubmit(), you have the option to return to the form view (in case of errors during saving or other situations that might require it). You can do so by calling showForm(HttpServletRequest request, BindException errors, String viewName). In its turn this will trigger the reference data method and return the appropriate view. One thing to keep in mind is that the BindException actually wraps the target (or command) object and if you capture the model returned from the showForm() call and go and meddle with the model yourself, you'll run into trouble. Replacing the command object somewhere during the process of submitting the form should be done prior to the onSubmit() callbacks, or by creating a new BindException, wrapping the new target in it, and passing it to the showForm() method.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net