3.8. Integrating Servlets and JSP: The MVC Architecture

Servlets are great when your application requires a lot of real programming to accomplish its task. Servlets can manipulate HTTP status codes and headers, use cookies, track sessions, save information between requests, compress pages, access databases, generate GIF images on-the-fly, and perform many other tasks flexibly and efficiently. But, generating HTML with servlets can be tedious and can yield a result that is hard to modify.

That’s where JSP comes in; it lets you separate much of the presentation from the dynamic content. That way, you can write the HTML in the normal manner, even using HTML-specific tools and putting your Web content developers to work on your JSP documents. JSP expressions, scriptlets, and declarations let you insert simple Java code into the servlet that results from the JSP page, and directives let you control the overall layout of the page. For more complex requirements, you can wrap up Java code inside beans or define your own JSP tags.

Great. We have everything we need, right? Well, no, not quite. The assumption behind a JSP document is that it provides a single overall presentation. What if you want to give totally different results depending on the data that you receive? Beans and custom tags (see Figure 3-28), although extremely powerful and flexible, don’t overcome the limitation that the JSP page defines a relatively fixed top-level page appearance. The solution is to use both servlets and JavaServer Pages. If you have a complicated application that may require several substantially different presentations, a servlet can handle the initial request, partially process the data, set up beans, and then forward the results to one of a number of different JSP pages, depending on the circumstances. This approach is known as the Model View Controller (MVC) or Model 2 architecture. For code that supports a formalization of this approach, see the Apache Struts Framework at http://jakarta.apache.org/struts/.

Figure 3-28. Strategies for invoking dynamic code from JSP.


Forwarding Requests

The key to letting servlets forward requests or include external content is to use a RequestDispatcher. You obtain a RequestDispatcher by calling the getRequestDispatcher method of ServletContext, supplying a URL relative to the server root. For example, to obtain a RequestDispatcher associated with http://yourhost/presentations/presentation1.jsp, you would do the following:

String url = "/presentations/presentation1.jsp"; 
RequestDispatcher dispatcher = 
  getServletContext().getRequestDispatcher(url); 

Once you have a RequestDispatcher, you use forward to completely transfer control to the associated URL and you use include to output the associated URL’s content. In both cases, you supply the HttpServletRequest and HttpServletResponse as arguments. Both methods throw ServletException and IOException. For example, Listing 3.57 shows a portion of a servlet that forwards the request to one of three different JSP pages, depending on the value of the operation parameter. To avoid repeating the getRequestDispatcher call, I use a utility method called gotoPage that takes the URL, the HttpServletRequest, and the HttpServletResponse; gets a RequestDispatcher; and then calls forward on it.

Listing 3.57. Request Forwarding Example
public void doGet(HttpServletRequest request, 
                  HttpServletResponse response) 
    throws ServletException, IOException {
  String operation = request.getParameter("operation"); 
  if (operation == null) {
    operation = "unknown"; 
  } 
  if (operation.equals("operation1")) {
    gotoPage("/operations/presentation1.jsp", 
             request, response); 
  } else if (operation.equals("operation2")) {
    gotoPage("/operations/presentation2.jsp", 
             request, response); 
  } else {
    gotoPage("/operations/unknownRequestHandler.jsp", 
             request, response); 
  } 
} 

private void gotoPage(String address, 
                      HttpServletRequest request, 
                      HttpServletResponse response) 
    throws ServletException, IOException {
  RequestDispatcher dispatcher =
							getServletContext().getRequestDispatcher(address);
							dispatcher.forward(request, response); 
} 

Using Static Resources

In most cases, you forward requests to a JSP page or another servlet. In some cases, however, you might want to send the request to a static HTML page. In an e-commerce site, for example, requests that indicate that the user does not have a valid account name might be forwarded to an account application page that uses HTML forms to gather the requisite information. With GET requests, forwarding requests to a static HTML page is perfectly legal and requires no special syntax; just supply the address of the HTML page as the argument to getRequestDispatcher. However, since forwarded requests use the same request method as the original request, POST requests cannot be forwarded to normal HTML pages. The solution to this problem is to simply rename the HTML page to have a.jsp extension. Renaming somefile.html to somefile.jsp does not change its output for GET requests, but somefile.html cannot handle POST requests, whereas somefile.jsp gives an identical response for both GET and POST.

Supplying Information to the Destination Pages

A servlet can store data for JSP pages in three main places: in the HttpServletRequest, in the HttpSession, and in the ServletContext. These storage locations correspond to the three nondefault values of the scope attribute of jsp:useBean: that is, request, session, and application.

  1. Storing data that servlet looked up and that JSP page will use only in this request. The servlet would create and store data as follows:

    SomeClass value = new SomeClass(...); 
    request.setAttribute("key", value);
    									

    Then, the servlet would forward to a JSP page that uses the following to retrieve the data:

    <jsp:useBean id="key" class="SomeClass" 
                 scope="request" /> 
  2. Storing data that servlet looked up and that JSP page will use in this request and in later requests from same client. The servlet would create and store data as follows:

    SomeClass value = new SomeClass(...); 
    HttpSession session = request.getSession(true);
    										session.setAttribute("key", value);
    									

    Then, the servlet would forward to a JSP page that uses the following to retrieve the data:

    <jsp:useBean id="key" class="SomeClass" 
                 scope="session" /> 
  3. Storing data that servlet looked up and that JSP page will use in this request and in later requests from any client. The servlet would create and store data as follows:

    SomeClass value = new SomeClass(...); 
    getServletContext().setAttribute("key", value);
    									

    Then, the servlet would forward to a JSP page that uses the following to retrieve the data:

    <jsp:useBean id="key" class="SomeClass" 
                 scope="application" /> 
Interpreting Relative URLs in the Destination Page

Although a servlet can forward the request to an arbitrary location on the same server, the process is quite different from that of using the sendRedirect method of HttpServletResponse. First, sendRedirect requires the client to reconnect to the new resource, whereas the forward method of RequestDispatcher is handled completely on the server. Second, sendRedirect does not automatically preserve all of the request data; forward does. Third, sendRedirect results in a different final URL, whereas with forward, the URL of the original servlet is maintained.

This final point means that if the destination page uses relative URLs for images or style sheets, it needs to make them relative to the server root, not to the destination page’s actual location. For example, consider the following style sheet entry:

<LINK REL=STYLESHEET 
      HREF="my-styles.css" 
      TYPE="text/css"> 

If the JSP page containing this entry is accessed by means of a forwarded request, my-styles.css will be interpreted relative to the URL of the originating servlet, not relative to the JSP page itself, almost certainly resulting in an error. Section 4.5 (Handling Relative URLs in Web Applications) discusses several approaches to this problem. One simple solution, however, is to give the full server path to the style sheet file, as follows.

<LINK REL=STYLESHEET 
      HREF="/path/my-styles.css" 
      TYPE="text/css"> 

The same approach is required for addresses used in <IMG SRC=...> and <A HREF=...>.

Using Alternative Means to Get a RequestDispatcher

Servers that support version 2.2 or 2.3 of the servlet specification have two additional ways of obtaining a RequestDispatcher besides the getRequestDispatcher method of ServletContext.

First, since most servers let you register explicit names for servlets or JSP pages, it makes sense to access them by name rather than by path. Use the getNamedDispatcher method of ServletContext for this task.

Second, you might want to access a resource by a path relative to the current servlet’s location, rather than relative to the server root. This approach is not common when servlets are accessed in the standard manner (http://host/servlet/ServletName), because JSP files would not be accessible by means of http://host/servlet/... since that URL is reserved especially for servlets. However, it is common to register servlets under another path (see Section 5.3, “Assigning Names and Custom URLs”), and in such a case you can use the getRequestDispatcher method of HttpServletRequest rather than the one from ServletContext. For example, if the originating servlet is at http://host/travel/TopLevel,

getServletContext().getRequestDispatcher("/travel/cruises.jsp") 

could be replaced by

request.getRequestDispatcher("cruises.jsp"); 

Example: An Online Travel Agent

Consider the case of an online travel agent that has a quick-search page, as shown in Figure 3-29 and Listing 3.58. Users need to enter their email address and password to associate the request with their previously established customer account. Each request also includes a trip origin, trip destination, start date, and end date. However, the action that will result will vary substantially in accordance with the action requested. For example, pressing the “Book Flights” button should show a list of available flights on the dates specified, ordered by price (see Figure 3-30). The user’s real name, frequent flyer information, and credit card number should be used to generate the page. On the other hand, selecting “Edit Account” should show any previously entered customer information, letting the user modify values or add entries. Likewise, the actions resulting from choosing “Rent Cars” or “Find Hotels” will share much of the same customer data but will have a totally different presentation.

Figure 3-29. Front end to travel servlet (see Listing 3.58).


Figure 3-30. Result of travel servlet (Listing 3.59) dispatching request to BookFlights.jsp (Listing 3.60).


To accomplish the desired behavior, the front end (Listing 3.58) submits the request to the top-level travel servlet shown in Listing 3.59. This servlet looks up the customer information (see http://www.moreservlets.com for the actual code used, but this would be replaced by a database lookup in real life), puts it in the HttpSession object associating the value (of type moreservlets.TravelCustomer) with the name customer, and then forwards the request to a different JSP page corresponding to each of the possible actions. The destination page (see Listing 3.60 and the result in Figure 3-30) looks up the customer information by means of

<jsp:useBean id="customer" 
             class="moreservlets.TravelCustomer" 
             scope="session" /> 

and then uses jsp:getProperty to insert customer information into various parts of the page.

Listing 3.58. quick-search.html (Excerpt)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> 
<HEAD> 
  <TITLE>Online Travel Quick Search</TITLE> 
  <LINK REL=STYLESHEET 
        HREF="travel-styles.css" 
        TYPE="text/css"> 
</HEAD> 
<BODY> 
<BR> 
<H1>Online Travel Quick Search</H1> 
<FORM ACTION="/servlet/moreservlets.Travel" METHOD="POST"> 
<CENTER> 
Email address: <INPUT TYPE="TEXT" NAME="emailAddress"><BR> 
Password: <INPUT TYPE="PASSWORD" NAME="password" SIZE=10><BR> 
... 
<TABLE CELLSPACING=1> 
<TR> 
  <TH>&nbsp;<IMG SRC="airplane.gif" WIDTH=100 HEIGHT=29 
                 ALIGN="TOP" ALT="Book Flight">&nbsp; 
 ... 
<TR> 
  <TH><SMALL> 
      <INPUT TYPE="SUBMIT" NAME="flights" VALUE="Book Flight"> 
      </SMALL> 
 ... 
</TABLE> 
</CENTER> 
</FORM> 
... 
</BODY> 
</HTML> 

Listing 3.59. Travel.java
package moreservlets; 

import java.io.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 

/** Top-level travel-processing servlet. This servlet sets up 
 *  the customer data as a bean, then forwards the request 
 *  to the airline booking page, the rental car reservation 
 *  page, the hotel page, the existing account modification 
 *  page, or the new account page. 
 */ 

public class Travel extends HttpServlet {
  private TravelCustomer[] travelData; 

  public void init() {
    travelData = TravelData.getTravelData(); 
  } 

  /** Since password is being sent, use POST only. However, 
   *  the use of POST means that you cannot forward 
   *  the request to a static HTML page, since the forwarded 
   *  request uses the same request method as the original 
   *  one, and static pages cannot handle POST. Solution: 
   *  have the "static" page be a JSP file that contains 
   *  HTML only. That's what accounts.jsp is. The other 
   *  JSP files really need to be dynamically generated, 
   *  since they make use of the customer data. 
   */ 

  public void doPost(HttpServletRequest request, 
                     HttpServletResponse response) 
      throws ServletException, IOException {
    String emailAddress = request.getParameter("emailAddress"); 
    String password = request.getParameter("password"); 
    TravelCustomer customer = 
      TravelCustomer.findCustomer(emailAddress, travelData); 
    if ((customer == null) || (password == null) || 
        (!password.equals(customer.getPassword()))) {
      gotoPage("/jsp-intro/travel/accounts.jsp",
							request, response); 
    } 
    // The methods that use the following parameters will 
    // check for missing or malformed values. 
    customer.setStartDate(request.getParameter("startDate")); 
    customer.setEndDate(request.getParameter("endDate")); 
    customer.setOrigin(request.getParameter("origin")); 
    customer.setDestination(request.getParameter 
                              ("destination")); 
    HttpSession session = request.getSession(true); 
    session.setAttribute("customer", customer); 
    if (request.getParameter("flights") != null) {
      gotoPage("/jsp-intro/travel/BookFlights.jsp",
							request, response); 
    } else if (request.getParameter("cars") != null) {
      gotoPage("/jsp-intro/travel/RentCars.jsp",
							request, response); 
    } else if (request.getParameter("hotels") != null) {
      gotoPage("/jsp-intro/travel/FindHotels.jsp",
							request, response); 
    } else if (request.getParameter("account") != null) {
      gotoPage("/jsp-intro/travel/EditAccounts.jsp",
							request, response); 
    } else {
      gotoPage("/jsp-intro/travel/IllegalRequest.jsp",
							request, response); 
    } 
  } 

  private void gotoPage(String address,
							HttpServletRequest request,
							HttpServletResponse response)
							throws ServletException, IOException {
							RequestDispatcher dispatcher =
							getServletContext().getRequestDispatcher(address);
							dispatcher.forward(request, response);
							} 
} 

Listing 3.60. BookFlights.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> 
<HEAD> 
  <TITLE>Best Available Flights</TITLE> 
  <LINK REL=STYLESHEET 
        HREF="/jsp-intro/travel/travel-styles.css" 
        TYPE="text/css"> 
</HEAD> 
<BODY> 
<H1>Best Available Flights</H1> 
<CENTER> 
<jsp:useBean id="customer"
							class="moreservlets.TravelCustomer"
							scope="session" /> 
Finding flights for 
<jsp:getProperty name="customer" property="fullName" /> 
<P> 
<jsp:getProperty name="customer" property="flights" /> 
<P><BR><HR><BR> 
<FORM ACTION="/servlet/BookFlight"> 
<jsp:getProperty name="customer"
							property="frequentFlyerTable" /> 
<P> 
<B>Credit Card:</B> 
<jsp:getProperty name="customer" property="creditCard" /> 
<P> 
<INPUT TYPE="SUBMIT" NAME="holdButton" VALUE="Hold for 24 Hrs"> 
<P> 
<INPUT TYPE="SUBMIT" NAME="bookItButton" VALUE="Book It!"> 
</FORM> 
</CENTER> 
</BODY> 
</HTML> 

You should pay careful attention to the TravelCustomer class (shown partially in Listing 3.61, with the complete code available at http://www.moreservlets.com). In particular, note that the class spends a considerable amount of effort making the customer information accessible as plain strings or even HTML-formatted strings through simple properties. Every task that requires any substantial amount of programming is spun off into the bean, rather than being performed in the JSP page itself. This is typical of servlet/JSP integration—the use of JSP does not entirely obviate the need to format data as strings or HTML in Java code. Significant up-front effort to make the data conveniently available to JSP more than pays for itself when multiple JSP pages access the same type of data. Other supporting classes (Frequent-FlyerInfo.java, TravelData.java, etc.), JSP pages (RentCars.jsp, FindHotels.jsp, etc.), and the travel-styles.css style sheet can be found at http://www.moreservlets.com.

Listing 3.61. TravelCustomer.java
package moreservlets; 

import java.util.*; 
import java.text.*; 

/** Describes a travel services customer. Implemented 
 *  as a bean with some methods that return data in HTML 
 *  format, suitable for access from JSP. 
 */ 

public class TravelCustomer {
  private String emailAddress, password, firstName, lastName; 
  private String creditCardName, creditCardNumber; 
  private String phoneNumber, homeAddress; 
  private String startDate, endDate; 
  private String origin, destination; 
  private FrequentFlyerInfo[] frequentFlyerData; 
  private RentalCarInfo[] rentalCarData; 
  private HotelInfo[] hotelData; 

  public TravelCustomer(String emailAddress, 
                        String password, 
                        String firstName, 
                        String lastName, 
                        String creditCardName, 
                        String creditCardNumber, 
                        String phoneNumber, 
                        String homeAddress, 
                        FrequentFlyerInfo[] frequentFlyerData, 
                        RentalCarInfo[] rentalCarData, 
                        HotelInfo[] hotelData) {
    setEmailAddress(emailAddress); 
    setPassword(password); 
    setFirstName(firstName); 
    setLastName(lastName); 
    setCreditCardName(creditCardName); 
    setCreditCardNumber(creditCardNumber); 
    setPhoneNumber(phoneNumber); 
    setHomeAddress(homeAddress); 
    setStartDate(startDate); 
    setEndDate(endDate); 
    setFrequentFlyerData(frequentFlyerData); 
    setRentalCarData(rentalCarData); 
    setHotelData(hotelData); 
  } 

  public String getEmailAddress() {
    return(emailAddress); 
  } 

  public void setEmailAddress(String emailAddress) {
    this.emailAddress = emailAddress; 
  } 

  // See http://www.moreservlets.com for missing code. 
  public String getFrequentFlyerTable() {
    FrequentFlyerInfo[] frequentFlyerData = 
      getFrequentFlyerData(); 
    if (frequentFlyerData.length == 0) {
      return("<I>No frequent flyer data recorded.</I>"); 
    } else {
      String table = 
        "<TABLE>
" + 
        "  <TR><TH>Airline<TH>Frequent Flyer Number
"; 
      for(int i=0; i<frequentFlyerData.length; i++) {
        FrequentFlyerInfo info = frequentFlyerData[i]; 
        table = table + 
                "<TR ALIGN="CENTER">" + 
                "<TD>" + info.getAirlineName() + 
                "<TD>" + info.getFrequentFlyerNumber() + "
"; 
      } 
      table = table + "</TABLE>
"; 
      return(table); 
    } 
  } 

  // This would be replaced by a database lookup 
  // in a real application. 

  public String getFlights() {
    String flightOrigin = 
      replaceIfMissing(getOrigin(), "Nowhere"); 
    String flightDestination = 
      replaceIfMissing(getDestination(), "Nowhere"); 
    Date today = new Date(); 
    DateFormat formatter = 
      DateFormat.getDateInstance(DateFormat.MEDIUM); 
    String dateString = formatter.format(today); 
    String flightStartDate = 
      replaceIfMissing(getStartDate(), dateString); 
    String flightEndDate = 
      replaceIfMissing(getEndDate(), dateString); 
    String [][] flights = 
      { { "Java Airways", "1522", "455.95", "Java, Indonesia", 
          "Sun Microsystems", "9:00", "3:15" }, 
        { "Servlet Express", "2622", "505.95", "New Atlanta", 
          "New Atlanta", "9:30", "4:15" }, 
        { "Geek Airlines", "3.14159", "675.00", "JHU", 
          "MIT", "10:02:37", "2:22:19" } }; 
    String flightString = ""; 
    for(int i=0; i<flights.length; i++) {
      String[] flightInfo = flights[i]; 
      flightString = 
        flightString + getFlightDescription(flightInfo[0], 
                                            flightInfo[1], 
                                            flightInfo[2], 
                                            flightInfo[3], 
                                            flightInfo[4], 
                                            flightInfo[5], 
                                            flightInfo[6], 
                                            flightOrigin, 
                                            flightDestination, 
                                            flightStartDate, 
                                            flightEndDate); 
    } 
    return(flightString); 
  } 
  private String getFlightDescription(String airline, 
                                      String flightNum, 
                                      String price, 
                                      String stop1, 
                                      String stop2, 
                                      String time1, 
                                      String time2, 
                                      String flightOrigin, 
                                      String flightDestination, 
                                      String flightStartDate, 
                                      String flightEndDate) {
    String flight = 
      "<P><BR>
" + 
      "<TABLE WIDTH="100%"><TR><TH CLASS="COLORED">
" + 
      "<B>" + airline + " Flight " + flightNum + 
      " ($" + price + ")</B></TABLE><BR>
" + 
      "<B>Outgoing:</B> Leaves " + flightOrigin + 
      " at " + time1 + " AM on " + flightStartDate + 
      ", arriving in " + flightDestination + 
      " at " + time2 + " PM (1 stop -- " + stop1 + ").
" + 
      "<BR>
" + 
      "<B>Return:</B> Leaves " + flightDestination + 
      " at " + time1 + " AM on " + flightEndDate + 
      ", arriving in " + flightOrigin + 
      " at " + time2 + " PM (1 stop -- " + stop2 + ").
"; 
    return(flight); 
  } 

  private String replaceIfMissing(String value, 
                                  String defaultValue) {
    if ((value != null) && (value.length() > 0)) {
      return(value); 
    } else {
      return(defaultValue); 
    } 
  } 

  public static TravelCustomer findCustomer 
                                 (String emailAddress, 
                                  TravelCustomer[] customers) {
    if (emailAddress == null) {
      return(null); 
    } 
    for(int i=0; i<customers.length; i++) {
      String custEmail = customers[i].getEmailAddress(); 
      if (emailAddress.equalsIgnoreCase(custEmail)) {
        return(customers[i]); 
      } 
    } 
    return(null); 
  } 
} 

Forwarding Requests from JSP Pages

The most common request-forwarding scenario is that the request first comes to a servlet and the servlet forwards the request to a JSP page. The reason a servlet usually handles the original request is that checking request parameters and setting up beans requires a lot of programming, and it is more convenient to do this programming in a servlet than in a JSP document. The reason that the destination page is usually a JSP document is that JSP simplifies the process of creating the HTML content.

However, just because this is the usual approach doesn’t mean that it is the only way of doing things. It is certainly possible for the destination page to be a servlet. Similarly, it is quite possible for a JSP page to forward requests elsewhere. For example, a request might go to a JSP page that normally presents results of a certain type and that forwards the request elsewhere only when it receives unexpected values.

Sending requests to servlets instead of JSP pages requires no changes whatsoever in the use of the RequestDispatcher. However, there is special syntactic support for forwarding requests from JSP pages. In JSP, the jsp:forward action is simpler and easier to use than wrapping up RequestDispatcher code in a scriptlet. This action takes the following form:

<jsp:forward page="Relative URL" /> 

The page attribute is allowed to contain JSP expressions so that the destination can be computed at request time. For example, the following code sends about half the visitors to http://host/examples/page1.jsp and the others to http://host/examples/page2.jsp.

<% String destination; 
   if (Math.random() > 0.5) {
     destination = "/examples/page1.jsp"; 
   } else {
     destination = "/examples/page2.jsp"; 
   } 
%> 
<jsp:forward page="<%= destination %>" /> 

The jsp:forward action, like jsp:include, can make use of jsp:param elements to supply extra request parameters to the destination page. For details, see the discussion of jsp:include in Section 3.5.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.68.28