JaVa
   

Implementing the Web Tier in the Sample app

We've already covered many of the concepts used in the web tier of our sample app. Let's summarize how it all fits together. Note that we won't look at JSP views: we'll look at view technologies in detail in the next chapter.

Overview

The web interface we'll discuss in this section is based on the business interfaces we've considered thus far, such as the Calendar and BoxOffice interfaces. It uses a single controller, com.wrox.expertj2ee.ticket.web.TicketController, which extends the MultiActionController framework superclass, discussed above, to handle all app URLs. View are JSP pages, which display model beans returned by the controller, which aren't web specific, such as the Reservation object. These JSP pages don't perform any request processing and include few scriptlets, as the controller makes all major decisions about view choice. Framework configuration involves:

Let's look at the complete definition of the TicketController bean again. Like all the XML fragments below, it comes from the app's /WEB-INF/ticket-servlet.xml file:

 <bean 
 class="com.wrox.expertj2ee.ticket.web.TicketController">
 <property beanRef="true">
 ticketControllerMethodNameResolver
 </property>
 <property beanRef="true">
 calendar
 </property>
 <property beanRef="true">
 boxOffice
 </property>
 <property beanRef="true">
 availabilityCheck
 </property>
 <property beanRef="true">
 userValidator
 </property>
 <property >3.50</property>
 <property >1</property>
 </bean>


Mappings from request URL to controller bean name are defined using a standard framework class as follows:

 <bean name="a.urlMap"
 class="com.interface21.web.servlet.UrlHandlerMapping">
 <property >
 /welcome.html=ticketController
 /show.html=ticketController
 /bookseats.html=ticketController
 /reservation.html=ticketController
 /payment.html=ticketController
 /confirmation.html=ticketController
 </property>
 </bean>


As the one controller handles all requests, all mappings are onto its bean name. The methodNameResolver property of the TicketController class is inherited from MultiActionController, and defines the mapping of request URLs to individual methods in the TicketController class. There is a mapping for each URL mapped onto the TicketController:

 <bean 
 class="com.interface21.web.servlet.mvc.multiaction.
 PropertiesMethodNameResolver">
 <property >
 /welcome.html=displayGenresPage
 /show.html=displayShow
 /bookseats.html=displayBookSeatsForm
 /reservation.html=processSeatSelectionFormSubmission
 /payment.html=displayPaymentForm
 /confirmation.html=processPaymentFormSubmission
 </property>
 </bean>


Handling a Seat Reservation Request

We've already seen the skeleton of the TicketController object. Let's conclude by looking at one complete request handling method, which will try to reserve a seat for a user. This method will process the selection form for a particular seat type in a particular performance, shown below:

Java Click To expand

When processing this form we can discount the likelihood of invalid form fields or type mismatches: two of the parameters are hidden form fields (performance ID and seat type ID) written by the app, and the user can only enter a number of seats from the dropdown, ensuring that all values are numeric. The result of form submission should be the following screen, if it is possible to reserve enough seats for the user:

Java Click To expand

If there aren't enough seats to fulfill the request, the user will be prompted to try another date, and a separate view will be displayed. The business requirements state that in the event of a successful tutorialing, the resulting Reservation object should be stored in the user's session. The user probably won't have an HttpSession object before this form is submitted. If the user refreshes or resubmits this form, the same reservation should be displayed. The method's name is reservation, matching the request URL reservation.html in our simple default mapping system. As error handling on invalid input isn't an issue, we can use the automatic data binding capability of the MultiActionController superclass. This will automatically create a new object of type ReservationRequest using Class.newInstance() and bind request parameters onto it. Thus the very invocation of this method indicates that we have a ReservationRequest containing the appropriate user information. Note that we simply declare this method to throw InvalidSeatingRequestException and NoSuchPerformanceException. These are fatal errors that cannot occur in normal operation (in fact they're only checked exceptions because we can't safely throw unchecked exceptions from EJB business methods). The MultiActionController superclass will let us throw any exception we please, and will throw a ServletException wrapping it.

NoteĀ 

We actually get a chance to handle the exception by implementing an exception handler method taking the exception or any of its superclasses (up to java.lang.Throwable) as a parameter. Such handler methods will be invoked on all methods in a MultiActionController. However, in this case there's little need for this functionality.

Note that it isn't usually a good idea to declare request handling methods simply to throw Exception; it's good to have to decide which app exceptions to catch and which to leave to the superclass, and to make this clear in the contract of the method, as we do here:

 public ModelAndView processSeatSelectionFormSubmission(
 HttpServletRequest request,
 HttpServletResponse response,
 ReservationRequest reservationRequest)
 throws ServletException,
 InvalidSeatingRequestException,
 NoSuchPerformanceException {


Once this method is invoked, the ReservationRequest object's properties have been successfully populated from request parameters. This method's first task is to add to the user-submitted information in the reservation request - performance ID, seat type ID, and the number of seats requested - standard information based on our current configuration. All the following instance variables in the TicketController class are set via the bean properties set in the XML bean definition element:

 reservationRequest.setBookingFee(this.bookingFee);
 reservationRequest.setReserve(true);
 reservationRequest.holdFor(this.minutesToHold);
 reservationRequest.setSeatsMustBeAdjacent(true);


With a fully configured ReservationRequest object, we can check any user session to see if there's a reservation that matches the request already, indicating resubmission of the same form data, which should prompt redisplay of the "Show Reservation" view. Note that we invoke the form of the request.getSession() method that takes a boolean parameter with a value of false: this doesn't create a session if none exists. Note that I've implemented the check whether a reservation satisfies a request in the Reservation class, not this web controller. This will enable its reuse in another interface; as such a check isn't web-specific:

 Reservation reservation = null;
 HttpSession session = request.getSession(false);
 if (session != null) {
 reservation = (Reservation) session.getAttribute(RESERVATION_KEY);
 if (reservation != null) {
 if (reservation.satisfiesRequest(reservationRequest)) {
 return new ModelAndView("showReservation", RESERVATION_KEY,
 reservation);
 } else {
 reservation = null;
 session.removeAttribute(RESERVATION_KEY);
 }
 }
 }


If this doesn't send the user straight to the "Show Reservation" view, we'll need to invoke the BoxOffice business object to try to create a Reservation. The TicketController's boxOffice instance variable is set via a bean property on app startup, so there's no work in looking up a BoxOffice implementation. The BoxOffice object's allocateSeats() method will either return a Reservation or throw a NotEnoughSeatsException, which we will catch and which will prompt the display of a different view. On creation of a successful reservation, we place the Reservation object in the user's session, creating a new session if necessary by calling request.getSession(true):

 try {
 reservation = boxOffice.allocateSeats(reservationRequest);
 session = request.getSession(true);
 session.setAttribute(RESERVATION_KEY, reservation);
 return new ModelAndView("showReservation", RESERVATION_KEY, reservation);
 }
 catch (NotEnoughSeatsException ex) {
 return new ModelAndView("notEnoughSeats", "exception", ex);
 }
 }


This request handling method contains no presentation-specific code. It deals only with business objects and model data: the "Show Reservation" view is free to display reservation confirmation any way it pleases, using any view technology. This method doesn't contain business logic: the rules it applies (such as "redisplay a reservation on receipt of a repeated form request") are tied to the user interface. All business processing is done by the BoxOffice interface.

In the next chapter we'll look at using different technologies, including JSP, XSLT, and WebMacro, to render the view for this screen.

Implementation Review

So, how did we do, overall, at meeting the goals we set ourselves for the web tier of our sample app? Did we achieve a clean web tier, with control flow controlled by Java objects and separated from presentation, handled by templates? There's no markup in Java code, and views such as JSP pages need to perform only iteration. As we'll see in the next chapter, views can be implemented without using scriptlets. We have not committed to a particular view technology, although we'll use JSP as the view technology because it's always available and there's no compelling reason to use another technology. Did we achieve a thin web tier, with a minimum code volume and the best possible separation of web interface from business logic?

Our web tier Java code consists of a single class, com.wrox.j2eedd.ticket.web.TicketController, which is under 500 lines in length. No other classes depend on the web framework or Servlet API, and no other classes are likely to be relevant only to a web interface. Input validation code is not dependant on web app framework or Servlet API. Were the number of screens in the app to grow, this controller might be broken into individual controllers. We even have the option of introducing additional controller servlets in really large apps. However, there's no benefit in using a greater number of classes now.

JaVa
   
Comments