Java Servlet Technology

 


Overview

A servlet is a Java programming language class used to execute Web server apps.

The javax.servlet and javax.servlet.http packages provide interfaces and classes for writing servlets. All servlets must implement the Servlet interface, which defines life-cycle methods.

When implementing a generic service, you can use or extend the GenericServlet class provided with the Java Servlet API. The HttpServlet class provides methods, such as doGet and doPost, for handling HTTP-specific services.

This chapter focuses on writing servlets that generate responses to HTTP requests.

 


The Example Servlets

This chapter uses the Duke's Bookstore app to illustrate the tasks involved in programming servlets. Each programming task is illustrated by one or more servlets. For example, BookDetailsServlet illustrates how to handle HTTP GET requests, BookDetailsServlet and CatalogServlet show how to construct responses, and CatalogServlet illustrates how to track session information.

Function Servlet
Enter the bookstore BookStoreServlet
Create the bookstore banner BannerServlet
Browse the bookstore catalog CatalogServlet
Put a book in a shopping cart CatalogServlet,
BookDetailsServlet
Get detailed information on a specific book BookDetailsServlet
Display the shopping cart ShowCartServlet
Remove one or more books from the shopping cart ShowCartServlet
Buy the books in the shopping cart CashierServlet
Receive an acknowledgement for the purchase ReceiptServlet

Here is the directory structure:


> cd $J2EE/web/bookstore1
> ls -CFR
.:
build.xml  context.xml  src/  web/

./src:
BannerServlet.java           ReceiptServlet.java       database/   util/
CashierServlet.java          exception/
BookDetailsServlet.java      ShowCartServlet.java      filters/
CatalogServlet.java          listeners/
BookStoreServlet.java        cart/                      messages/

./src/cart:
ShoppingCart.java  ShoppingCartItem.java  

./src/database:
BookDB.java  BookDetails.java  

./src/exception:
BookNotFoundException.java      BooksNotFoundException.java      OrderException.java


./src/filters:
CharResponseWrapper.java      HitCounterFilter.java      OrderFilter.java


./src/listeners:
ContextListener.java  

./src/messages:
BookstoreMessages.java  BookstoreMessages_es.java  

./src/util:
Counter.java  Currency.java  

./web:
WEB-INF/  duke.books.gif  errorpage.html

./web/WEB-INF:
web.xml*  

The data for the bookstore app is maintained in a database and accessed through the helper class database.BookDB. The database package also contains the class BookDetails, which represents a book. The shopping cart and shopping cart items are represented by the classes cart.ShoppingCart and cart.ShoppingCartItem, respectively.

To build, deploy, and run the example, follow these steps.

  1. Go to...

    $J2EE_HOME/examples

    ...and build the example by running

    ant bookstore1

  2. Start the j2ee server.

  3. Start deploytool.

  4. Start the Cloudscape database server by running cloudscape -start.

  5. Load the bookstore data into the database by running

    ant create-web-db.

  6. Create a J2EE app called Bookstore1App.

    • Select FileNewApplication.

    • In the file chooser, navigate to

      $J2EE_HOME/examples/src/web/bookstore1.

    • In the File Name field, enter Bookstore1App.

    • Click New Application and OK.

  7. Create the WAR and add the BannerServlet Web component and all of the Duke's Bookstore content to the Bookstore1App app.

    1. Select FileNewWeb Component.

    2. Click the Create New WAR File In Application radio button and select Bookstore1App from the combo box. Enter Bookstore1WAR in the field labeled WAR Display Name.

    3. Click Edit to add the content files.

    4. In the Edit Archive Contents dialog box, navigate to...

      $J2EE_HOME/bookstore1

      Select BannerServlet.class, BookStoreServlet.class, BookDetailsServlet.class, CatalogServlet.class, ShowCartServlet.class, CashierServlet.class, and ReceiptServlet.class. Click Add. Add errorpage.html and duke.books.gif. Add the cart, database, exception, filters, listeners, messages, and util packages. Click OK.

    5. Click Next.

    6. Select the Servlet radio button.

    7. Click Next.

    8. Select BannerServlet from the Servlet Class combo box.

    9. Click Next twice.

    10. In the Component Aliases pane, click Add and then type /banner in the Alias field.

    11. Click Finish.

  8. Add each of the Web components listed in the table below. For each servlet, click the Add to Existing WAR File radio button and select Bookstore1WAR from the combo box. Since the WAR contains all of the servlet classes, you do not have to add any more content.

     

    Web Component Name Servlet Class Component Alias
    BookStoreServlet BookStoreServlet /enter
    CatalogServlet CatalogServlet /catalog
    BookDetailsServlet BookDetailsServlet /bookdetails
    ShowCartServlet ShowCartServlet /showcart
    CashierServlet CashierServlet /cashier
    ReceiptServlet ReceiptServlet /receipt

  9. Add a resource reference for the Cloudscape database.

    1. Select Bookstore1WAR.

    2. Select the Resource Refs tab.

    3. Click Add.

    4. Select javax.sql.DataSource from the Type column

    5. Enter jdbc/BookDB in the Coded Name field.

    6. Enter jdbc/Cloudscape in the JNDI Name field.

  10. Add the listener class listeners.ContextListener

    1. Select the Event Listeners tab.

    2. Click Add.

    3. Select the listeners.ContextListener class from the drop-down field in the Event Listener Classes pane.

  11. Add an error page.

    1. Select the File Refs tab.

    2. In the Error Mapping panel, click Add.

    3. Enter exception.BookNotFoundException in the Error/Exception field.

    4. Enter /errorpage.html in the Resource To Be Called field.

    5. Repeat for exception.BooksNotFoundException and javax.servlet.UnavailableException.

  12. Add the filters filters.HitCounterFilter and filters.OrderFilter.

    1. Select the Filter Mapping tab.

    2. Click Edit Filter List.

    3. Click Add.

    4. Select filters.HitCounterFilter from the Filter Class column. The deploytool utility will automatically enter HitCounterFilter in the Display Name column.

    5. Click Add.

    6. Select filters.OrderFilter from the Filter Class column. The deploytool utility will automatically enter OrderFilter in the Display Name column.

    7. Click OK.

    8. Click Add.

    9. Select HitCounterFilter from the Filter Name column.

    10. Select Servlet from the Target Type column.

    11. Select BookStoreServlet from the Target column.

    12. Repeat for OrderFilter. The target type is Servlet and the target is ReceiptServlet.

  13. Enter the context root.

    1. Select Bookstore1App.

    2. Select the Web Context tab.

    3. Enter bookstore1.

  14. Deploy the app.

    1. Select ToolsDeploy.

    2. Click Finish.

  15. Open the bookstore URL http://<host>:8000/bookstore1/enter.

 

Troubleshooting

The section Common Problems and Their Solutions (in particular, Web Client Runtime Errors) lists some reasons why a Web client can fail. In addition, Duke's Bookstore returns the following exceptions:

  • BookNotFoundException: Returned if a book can't be located in the bookstore database. This will occur if you haven't loaded the bookstore database with data by running...

    ant create-web-db

    ...or if the Cloudscape server hasn't been started or it has crashed.

  • BooksNotFoundException: Returned if the bookstore data can't be retrieved. This will occur if you haven't loaded the bookstore database with data by running...

    ant create-web-db

    ...or if the Cloudscape server hasn't been started or has crashed.

  • UnavailableException: Returned if a servlet can't retrieve the Web context attribute representing the bookstore. This will occur if you haven't added the listener class to the app.

Since we have specified an error page, you will see the message The app is unavailable. Please try later. If you don't specify an error page, the Web container generates a default page containing the message A Servlet Exception Has Occurred and a stack trace that can help diagnose the cause of the exception. If you use errorpage.html, you will have to look in the Web container's log to determine the cause of the exception. Web log files reside in the directory

$J2EE_HOME/logs/<host>/web

and are named catalina.<date>.log.

The <logs> element is the directory specified by the log.directory entry in the default.properties file. The default value is logs. The <host> element is the name of the computer.

 


Servlet Life Cycle

The life cycle of a servlet is controlled by the container in which the servlet has been deployed. When a request is mapped to a servlet, the container performs the following steps.

  1. If an instance of the servlet does not exist, the Web container

    1. Loads the servlet class.

    2. Creates an instance of the servlet class.

    3. Initializes the servlet instance by calling the init method.

  2. Invokes the service method, passing a request and response object.

If the container needs to remove the servlet, it finalizes the servlet by calling the servlet's destroy method.

 

Handling Servlet Life-Cycle Events

You can monitor and react to events in a servlet's life cycle by defining listener objects whose methods get invoked when life cycle events occur. To use these listener objects, define the listener class and specify the listener class.

Defining The Listener Class

You define a listener class as an implementation of a listener interface. When a listener method is invoked, it is passed an event that contains information appropriate to the event. For example, the methods in the HttpSessionListener interface are passed an HttpSessionEvent, which contains an HttpSession.

 

Object Event Listener Interface and Event Class
Web context Initialization and destruction javax.servlet.ServletContextListener and
ServletContextEvent
Attribute added, removed, or replaced javax.servlet.ServletContextAttributeListener and
ServletContextAttributeEvent
Session Creation, invalidation, and timeout javax.servlet.http.HttpSessionListener and
HttpSessionEvent
Attribute added, removed, or replaced javax.servlet.http.HttpSessionAttributeListener and
HttpSessionBindingEvent

The listeners.ContextListener class creates and removes the database helper and counter objects used in the Duke's Bookstore app. The methods retrieve the Web context object from ServletContextEvent and then store (and remove) the objects as servlet context attributes.

import database.BookDB;
import javax.servlet.*;
import util.Counter;

public final class ContextListener
   implements ServletContextListener {
   private ServletContext context = null;
   public void contextInitialized(ServletContextEvent event) {
      context = event.getServletContext();
      try {
         BookDB bookDB = new BookDB();
         context.setAttribute("bookDB", bookDB);
      } catch (Exception ex) {
         System.out.println(
            "Couldn't create database: " + ex.getMessage());
      }
      Counter counter = new Counter();
      context.setAttribute("hitCounter", counter);
      context.log("Created hitCounter" + 
         counter.getCounter());
      counter = new Counter();
      context.setAttribute("orderCounter", counter);
      context.log("Created orderCounter" +
         counter.getCounter());
   }

   public void contextDestroyed(ServletContextEvent event) {
      context = event.getServletContext();
      BookDB bookDB = context.getAttribute(
         "bookDB");
      bookDB.remove();
      context.removeAttribute("bookDB");
      context.removeAttribute("hitCounter");
      context.removeAttribute("orderCounter");
   }
}

Specifying Event Listener Classes

You specify a listener class for a WAR in the deploytool Event Listeners inspector.

 

Handling Errors

Any number of exceptions can occur when a servlet is executed. The Web container will generate a default page containing the message A Servlet Exception Has Occurred when an exception occurs, but you can also specify that the container should return a specific error page for a given exception. You specify error pages for a WAR in the deploytool File Refs inspector.

 


Sharing Information

Web components, like most objects, usually work with other objects to accomplish their tasks. There are several ways they can do this. They can use private helper objects (for example, JavaBeans components), they can share objects that are attributes of a public scope, they can use a database, and they can invoke other Web resources.

 

Using Scope Objects

Collaborating Web components share information via objects maintained as attributes of four scope objects. These attributes are accessed with the [get|set]Attribute methods of the class representing the scope.

 

Scope Object Class Accessible From
Web context javax.servlet.ServletContext Web components within a Web context
session javax.servlet.http.HttpSession Web components handling a request that belongs to the session
request Subtype of javax.servlet.ServletRequest Web components handling the request
page javax.servlet.jsp.PageContext The JSP page that creates the object
.

Figure 10-1 Duke's Bookstore Scoped Attributes

 

Controlling Concurrent Access to Shared Resources

In a multithreaded server, it is possible for shared resources to be accessed concurrently. Besides scope object attributes, shared resources include in-memory data such as instance or class variables, and external objects such as files, database connections, and network connections. Concurrent access can arise in several situations:

  • Multiple Web components accessing objects stored in the Web context

  • Multiple Web components accessing objects stored in a session

  • Multiple threads within a Web component accessing instance variables. A Web container will typically create a thread to handle each request. If you want to ensure that a servlet instance handles only one request at a time, a servlet can implement the SingleThreadModel interface. If a servlet implements this interface, you are guaranteed that no two threads will execute concurrently in the servlet's service method. A Web container can implement this guarantee by synchronizing access to a single instance of the servlet, or by maintaining a pool of Web component instances and dispatching each new request to a free instance. This interface does not prevent synchronization problems that result from Web components accessing shared resources such as static class variables or external objects.

When resources can be accessed concurrently, they can be used in an inconsistent fashion. To prevent this, control the access using the synchronization techniques described in the Threads.

In the previous section we showed five scoped attributes shared by more than one servlet: bookDB, cart, currency, hitCounter, and orderCounter. The bookDB attribute is discussed in the next section. The cart, currency, and counters can be set and read by multiple multithreaded servlets. To prevent these objects from being used inconsistently, access is controlled by synchronized methods. For example, here is the util.Counter class:

public class Counter {
   private int counter;
   public Counter() {
      counter = 0;
   }
   public synchronized int getCounter() {
      return counter;
   }
   public synchronized int setCounter(int c) {
      counter = c;
      return counter;
   }
   public synchronized int incCounter() {
      return(++counter);
   }
}

 

Accessing Databases

Data that is shared between Web components and is persistent between invocations of a J2EE app is usually maintained by a database. Web components use the JDBC 2.0 API to access relational databases. The data for the bookstore app is maintained in a database and accessed through the helper class database.BookDB. For example, ReceiptServlet invokes the BookDB.buyBooks method to update the book inventory when a user makes a purchase. The buyBooks method invokes buyBook for each book contained in the shopping cart. To ensure the order is processed in its entirety, the calls to buyBook are wrapped in a single JDBC transaction. The use of the shared database connection is synchronized via the [get|release]Connection methods.

public void buyBooks(ShoppingCart cart) throws OrderException {
   Collection items = cart.getItems();
   Iterator i = items.iterator();
   try { 
      getConnection(); 
      con.setAutoCommit(false); 
      while (i.hasNext()) {
         ShoppingCartItem sci = (ShoppingCartItem)i.next();
         BookDetails bd = (BookDetails)sci.getItem();
         String id = bd.getBookId();
         int quantity = sci.getQuantity();
         buyBook(id, quantity);
      }
      con.commit();
      con.setAutoCommit(true); 
      releaseConnection(); 
   } catch (Exception ex) { 
      try { 
      con.rollback(); 
      releaseConnection(); 
      throw new OrderException("Transaction failed: " +
         ex.getMessage()); 
      } catch (SQLException sqx) { 
         releaseConnection(); 
         throw new OrderException("Rollback failed: " +
            sqx.getMessage()); 
      } 
   } 
}

 


Initializing a Servlet

After the Web container loads and instantiates the servlet class and before it delivers requests from clients, the Web container initializes the servlet. You can customize this process to allow the servlet to read persistent configuration data, initialize resources, and perform any other one-time activities by overriding the init method of the Servlet interface. A servlet that cannot complete its initialization process should throw UnavailableException.

All the servlets that access the bookstore database (BookStoreServlet, CatalogServlet, BookDetailsServlet, and ShowCartServlet) initialize a variable in their init method that points to the database helper object created by the Web context listener:

public class CatalogServlet extends HttpServlet {
   private BookDB bookDB;
   public void init() throws ServletException {
      bookDB = (BookDB)getServletContext().
         getAttribute("bookDB");
      if (bookDB == null) throw new
         UnavailableException("Couldn't get database.");
   }
}

 


Writing Service Methods

The service provided by a servlet is implemented in the service method of a GenericServlet, the doMethod methods (where Method can take the value Get, Delete, Options, Post, Put, Trace) of an HttpServlet, or any other protocol-specific methods defined by a class that implements the Servlet interface. In the rest of this chapter, the term service method will be used for any method in a servlet class that provides a service to a client.

The general pattern for a service method is to extract information from the request, access external resources, and then populate the response based on that information.

For HTTP servlets, the correct procedure for populating the response is to first fill in the response headers, then retrieve an output stream from the response, and finally write any body content to the output stream. Response headers must always be set before a PrintWriter or ServletOutputStream is retrieved because the HTTP protocol expects to receive all headers before body content. The next two sections describe how to get information from requests and generate responses.

 

Getting Information from Requests

A request contains data passed between a client and the servlet. All requests implement the ServletRequest interface. This interface defines methods for accessing the following information:

  • Parameters, which are typically used to convey information between clients and servlets

  • Object-valued attributes, which are typically used to pass information between the servlet container and a servlet or between collaborating servlets

  • Information about the protocol used to communicate the request and the client and server involved in the request

  • Information relevant to localization

For example, in CatalogServlet the identifier of the book that a customer wishes to purchase is included as a parameter to the request. The following code fragment illustrates how to use the getParameter method to extract the identifier:

String bookId = request.getParameter("Add");
if (bookId != null) {
   BookDetails book = bookDB.getBookDetails(bookId);

You can also retrieve an input stream from the request and manually parse the data. To read character data, use the BufferedReader object returned by the request's getReader method. To read binary data, use the ServletInputStream object returned by getInputStream.

HTTP servlets are passed an HTTP request object, HttpServletRequest, which contains the request URL, HTTP headers, query string, and so on.

An HTTP request URL contains the following parts:

http://<host>:<port><request path>?<query string>

The request path is further composed of the following elements:

  • Context path: A concatenation of a forward slash (/) with the context root of the servlet's J2EE app.

  • Servlet path: The path section that corresponds to the component alias that activated this request. This path starts with a forward slash (/).

  • Path info: The part of the request path that is not part of the context path or the servlet path.

If the context path is /catalog, here are some examples of how the URL will be broken down:

 

Pattern Servlet
/lawn/* LawnServlet
/*.jsp JSPServlet

 

Request Path Servlet Path Path Info
/catalog/lawn/index.html /lawn /index.html
/catalog/help/feedback.jsp /help/feedback.jsp null

Query strings are composed of a set of parameters and values. Individual parameters are retrieved from a request with the getParameter method. There are two ways to generate query strings:

  • A query string can explicitly appear in a Web page. For example, an HTML page generated by CatalogServlet could contain the link

       <a href="/bookstore1/catalog?Add=101">Add To Cart</a>
    
    CatalogServlet extracts the parameter named Add as follows:

       String bookId = request.getParameter("Add");
    

  • A query string is appended to a URL when a form with a GET HTTP method is submitted. In the Duke's Bookstore app, CashierServlet generates a form, then a user name input to the form is appended to the URL that maps to ReceiptServlet, and finally ReceiptServlet extracts the user name using the getParameter method.

 

Constructing Responses

A response contains data passed between a server and the client. All responses implement the ServletResponse interface. This interface defines methods that allow you to do the following:

  • Retrieve an output stream to use to send data to the client. To send character data, use the PrintWriter returned by the response's getWriter method. To send binary data in a MIME body response, use the ServletOutputStream returned by getOutputStream. To mix binary and text data, for example, to create a multipart response, use a ServletOutputStream and manage the character sections manually.

  • Indicate the content type (for example, text/html), being returned by the response. A registry of content type names is kept by the Internet Assigned Numbers Authority (IANA) at:

       ftp://ftp.isi.edu/in-notes/iana/assignments/media-types
    

  • Indicate whether to buffer output. By default, any content written to the output stream is immediately sent to the client. Buffering allows content to be written before anything is actually sent back to the client, thus providing the servlet with more time to set appropriate status codes and headers or forward to another Web resource.

  • Set localization information.

HTTP response objects, HttpServletResponse, have fields representing HTTP headers such as

  • Status codes, which are used to indicate the reason a request is not satisfied.

  • Cookies, which are used to store app-specific information at the client. Sometimes cookies are used to maintain an identifier for tracking a user's session.

In Duke's Bookstore, BookDetailsServlet generates an HTML page that displays information about a book that the servlet retrieves from a database. The servlet first sets response headers: the content type of the response and the buffer size. The servlet buffers the page content because the database access can generate an exception that would cause forwarding to an error page. By buffering the response, the client will not see a concatenation of part of a Duke's Bookstore page with the error page should an error occur. The doGet method then retrieves a PrintWriter from the response.

For filling in the response, the servlet first dispatches the request to BannerServlet, which generates a common banner for all the servlets in the app. Then the servlet retrieves the book identifier from a request parameter and uses the identifier to retrieve information about the book from the bookstore database. Finally, the servlet generates HTML markup that describes the book information and commits the response to the client by calling the close method on the PrintWriter.

public class BookDetailsServlet extends HttpServlet { 
    public void doGet (HttpServletRequest request,
         HttpServletResponse response)
         throws ServletException, IOException {
      // set headers before accessing the Writer
      response.setContentType("text/html");
      response.setBufferSize(8192);
      PrintWriter out = response.getWriter();

      // then write the response
      out.println("<html>" +
         "<head><title>+
         messages.getString("TitleBookDescription")
         +</title></head>");

      // Get the dispatcher; it gets the banner to the user
      RequestDispatcher dispatcher =
         getServletContext().
         getRequestDispatcher("/banner");
      if (dispatcher != null)
         dispatcher.include(request, response);

      //Get the identifier of the book to display
      String bookId = request.getParameter("bookId");
      if (bookId != null) {
         // and the information about the book
         try {
            BookDetails bd =
               bookDB.getBookDetails(bookId);
            ...
            //Print out the information obtained
            out.println("<h2>" + bd.getTitle() + "</h2>" +
            ...
         } catch (BookNotFoundException ex) {
            response.resetBuffer();
            throw new ServletException(ex);
         }
      }
      out.println("</body></html>");
      out.close();
   }
}

 


Filtering Requests and Responses

A filter is an object that can transform the header or content or both of a request or response. Filters differ from Web components in that they usually do not themselves create a response. Instead, a filter provides functionality that can be "attached" to any kind of Web resource. As a consequence, a filter should not have any dependencies on a Web resource for which it is acting as a filter, so that it can be composable with more than one type of Web resource. The main tasks that a filter can perform are as follows:

  • Query the request and act accordingly.

  • Block the request-and-response pair from passing any further.

  • Modify the request headers and data. You do this by providing a customized version of the request.

  • Modify the response headers and data. You do this by providing a customized version of the response.

  • Interact with external resources.

Figure 10-2 Book Details

Applications of filters include authentication, logging, image conversion, data compression, encryption, tokenizing streams, and XML transformations.

You can configure a Web resource to be filtered by a chain of zero, one, or more filters in a specific order. This chain is specified when the Web app containing the component is deployed and is instantiated when a Web container loads the component.

In summary, the tasks involved in using filters include

  • Programming the filter

  • Programming customized requests and responses

  • Specifying the filter chain for each Web resource

 

Programming Filters

The filtering API is defined by the Filter, FilterChain, and FilterConfig interfaces in the javax.servlet package. You define a filter by implementing the Filter interface. The most important method in this interface is the doFilter method, which is passed request, response, and filter chain objects. This method can perform the following actions:

  • Examine the request headers.

  • Customize the request object if it wishes to modify request headers or data.

  • Customize the response object if it wishes to modify response headers or data.

  • Invoke the next entity in the filter chain. If the current filter is the last filter in the chain that ends with the target Web component or static resource, the next entity is the resource at the end of the chain; otherwise, it is the next filter that was configured in the WAR. It invokes the next entity by calling the doFilter method on the chain object (passing in the request and response it was called with, or the wrapped versions it may have created). Alternatively, it can choose to block the request by not making the call to invoke the next entity. In the latter case, the filter is responsible for filling out the response.

  • Examine response headers after it has invoked the next filter in the chain.

  • Throw an exception to indicate an error in processing.

In addition to doFilter, implement the init and destroy methods. The init method is called by the container when the filter is instantiated. If you wish to pass initialization parameters to the filter, you retrieve them from the FilterConfig object passed to init.

The Duke's Bookstore app uses the filters HitCounterFilter and OrderFilter to increment and log the value of a counter when the entry and receipt servlets are accessed.

In the doFilter method, both filters retrieve the servlet context from the filter configuration object so that they can access the counters stored as context attributes. After the filters have completed app-specific processing, they invoke doFilter on the filter chain object passed into the original doFilter method. The elided code is discussed in the next section.

public final class HitCounterFilter implements Filter {
   private FilterConfig filterConfig = null;

   public void init(FilterConfig filterConfig) 
      throws ServletException {
      this.filterConfig = filterConfig;
   }
   public void destroy() {
      this.filterConfig = null;
   }
   public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain) 
      throws IOException, ServletException {
      if (filterConfig == null)
         return;
      StringWriter sw = new StringWriter();
      PrintWriter writer = new PrintWriter(sw);
      Counter counter = (Counter)filterConfig.
         getServletContext().
         getAttribute("hitCounter");
      writer.println();
      writer.println("===============");
      writer.println("The number of hits is: " +
         counter.incCounter());
      writer.println("===============");
      // Log the resulting string
      writer.flush();
      filterConfig.getServletContext().
         log(sw.getBuffer().toString());
      ...
      chain.doFilter(request, wrapper);
      ...
   }
}

 

Programming Customized Requests and Responses

There are many ways for a filter to modify a request or response. For example, a filter could add an attribute to the request or insert data in the response. In the Duke's Bookstore example, HitCounterFilter inserts the value of the counter into the response.

A filter that modifies a response must usually capture the response before it is returned to the client. The way to do this is to pass a stand-in stream to the servlet that generates the response. The stand-in stream prevents the servlet from closing the original response stream when it completes and allows the filter to modify the servlet's response.

To pass this stand-in stream to the servlet, the filter creates a response wrapper that overrides the getWriter or getOutputStream method to return this stand-in stream. The wrapper is passed to the doFilter method of the filter chain. Wrapper methods default to calling through to the wrapped request or response object. This approach follows the well-known Wrapper or Decorator pattern described in Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995). The following sections describe how the hit counter filter described earlier and other types of filters use wrappers.

To override request methods, you wrap the request in an object that extends ServletRequestWrapper or HttpServletRequestWrapper. To override response methods, you wrap the response in an object that extends ServletResponseWrapper or HttpServletResponseWrapper.

HitCounterFilter wraps the response in a CharResponseWrapper. The wrapped response is passed to the next object in the filter chain, which is BookStoreServlet. BookStoreServlet writes its response into the stream created by CharResponseWrapper. When chain.doFilter returns, HitCounterFilter retrieves the servlet's response from PrintWriter and writes it to a buffer. The filter inserts the value of the counter into the buffer, resets the content length header of the response, and finally writes the contents of the buffer to the response stream.

PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper(
   (HttpServletResponse)response);
chain.doFilter(request, wrapper);
CharArrayWriter caw = new CharArrayWriter();
caw.write(wrapper.toString().substring(0,
   wrapper.toString().indexOf("</body>")-1));
caw.write("<p>\n<center>" + 
   messages.getString("Visitor") + "<font color='red'>" + 
   counter.getCounter() + "</font></center>");
caw.write("\n</body></html>");
response.setContentLength(caw.toString().length());
out.write(caw.toString());
out.close();

public class CharResponseWrapper extends
   HttpServletResponseWrapper {
   private CharArrayWriter output;
   public String toString() {
      return output.toString();
   }
   public CharResponseWrapper(HttpServletResponse response){
      super(response);
      output = new CharArrayWriter();
   }
   public PrintWriter getWriter(){
      return new PrintWriter(output);
   }
}

Figure 10-3 Duke's Bookstore Entry Page

 

Specifying Filter Mappings

A Web container uses filter mappings to decide how to apply filters to Web resources. A filter mapping matches a filter to a Web component by name or to Web resources by URL pattern. The filters are invoked in the order in which filter mappings appear in the filter mapping list of a WAR. You specify a filter mapping list for a WAR in the deploytool Filter Mapping inspector.

Filter mapping list for the Duke's Bookstore app. The filters are matched by servlet name and each filter chain contains only one filter.

 

Servlet Name Filter
BookStoreServlet HitCounterFilter
ReceiptServlet OrderFilter

You can map a filter to one or more Web resources, and you can map more than one filter to a Web resource. This is illustrated in below, where filter F1 is mapped to servlets S1, S2, and S3, filter F2 is mapped to servlet S2, and filter F3 is mapped to servlets S1 and S2.

Figure 10-4 Filter to Servlet Mapping

Recall that a filter chain is one of the objects passed to the doFilter method of a filter. This chain is formed indirectly via filter mappings. The order of the filters in the chain is the same as the order in which filter mappings appear in the Web app deployment descriptor.

When a filter is mapped to servlet S1, the Web container invokes the doFilter method of F1. The doFilter method of each filter in S1's filter chain is invoked by the preceding filter in the chain via the chain.doFilter method. Since S1's filter chain contains filters F1 and F3, F1's call to chain.doFilter invokes the doFilter method of filter F3. When F3's doFilter method completes, control returns to F1's doFilter method.

 


Invoking Other Web Resources

Web components can invoke other Web resources in two ways: indirect and direct. A Web component indirectly invokes another Web resource when it embeds in content returned to a client a URL that points to another Web component. In the Duke's Bookstore app, most Web components contain embedded URLs that point to other Web components. For example, ShowCartServlet indirectly invokes the CatalogServlet through the embedded URL /bookstore1/catalog.

A Web component can also directly invoke another resource while it is executing. There are two possibilities: it can include the content of another resource, or it can forward a request to another resource.

To invoke a resource available on the server that is running a Web component, first obtain a RequestDispatcher object using the getRequestDispatcher("URL") method.

You can get a RequestDispatcher object from either a request or the Web context; however, the two methods have slightly different behavior. The method takes the path to the requested resource as an argument. A request can take a relative path (that is, one that does not begin with a /), but the Web context requires an absolute path. If the resource is not available, or if the server has not implemented a RequestDispatcher object for that type of resource, getRequestDispatcher will return null. Your servlet should be prepared to deal with this condition.

 

Including Other Resources in the Response

It is often useful to include another Web resource, for example, banner content or copyright information, in the response returned from a Web component. To include another resource, invoke the include method of a RequestDispatcher object:

include(request, response);

If the resource is static, the include method enables programmatic server-side includes. If the resource is a Web component, the effect of the method is to send the request to the included Web component, execute the Web component, and then include the result of the execution in the response from the containing servlet. An included Web component has access to the request object, but it is limited in what it can do with the response object:

  • It can write to the body of the response and commit a response.

  • It cannot set headers or call any method (for example, setCookie) that affects the headers of the response.

The banner for the Duke's Bookstore app is generated by BannerServlet. Note that both the doGet and doPost methods are implemented because BannerServlet can be dispatched from either method in a calling servlet.

public class BannerServlet extends HttpServlet { 
   public void doGet (HttpServletRequest request,
      HttpServletResponse response)
      throws ServletException, IOException {

      PrintWriter out = response.getWriter();
      out.println("<body bgcolor=\"#ffffff\">" +
      "<center>" + "<hr> <br> &nbsp;" + "<h1>" +
      "<font size=\"+3\" color=\"#CC0066\">Duke's </font>" +
      <img src=\"" + request.getContextPath() +
      "/duke.books.gif\">" + 
      "<font size=\"+3\" color=\"black\">Bookstore</font>" +
      "</h1>" + "</center>" + "<br> &nbsp; <hr> <br> ");
   }
   public void doPost (HttpServletRequest request,
      HttpServletResponse response)
      throws ServletException, IOException {

      PrintWriter out = response.getWriter();
      out.println("<body bgcolor=\"#ffffff\">" +
      "<center>" + "<hr> <br> &nbsp;" + "<h1>" +
      "<font size=\"+3\" color=\"#CC0066\">Duke's </font>" +
      <img src=\"" + request.getContextPath() +
      "/duke.books.gif\">" + 
      "<font size=\"+3\" color=\"black\">Bookstore</font>" +
      "</h1>" + "</center>" + "<br> &nbsp; <hr> <br> ");
   }
}

Each servlet in the Duke's Bookstore app includes the result from BannerServlet with the following code:

RequestDispatcher dispatcher =
   getServletContext().getRequestDispatcher("/banner");
if (dispatcher != null)
   dispatcher.include(request, response);
} 

 

Transferring Control to Another Web Component

In some apps, you might want to have one Web component do preliminary processing of a request and have another component generate the response. For example, you might want to partially process a request and then transfer to another component depending on the nature of the request.

To transfer control to another Web component, you invoke the forward method of a RequestDispatcher. When a request is forwarded, the request URL is set to the path of the forwarded page. If the original URL is required for any processing, you can save it as a request attribute. The Dispatcher servlet, used by a version of the Duke's Bookstore app described in the section A Template Tag Library, saves the path information from the original URL, retrieves a RequestDispatcher from the request, and then forwards to the JSP page template.jsp.

public class Dispatcher extends HttpServlet {
   public void doGet(HttpServletRequest request, 
      HttpServletResponse response) {
      request.setAttribute("selectedScreen",
         request.getServletPath());
      RequestDispatcher dispatcher = request.
         getRequestDispatcher("/template.jsp");
      if (dispatcher != null)
         dispatcher.forward(request, response);
   }
   public void doPost(HttpServletRequest request, 
   ...
}

The forward method should be used to give another resource responsibility for replying to the user. If you have already accessed a ServletOutputStream or PrintWriter object within the servlet, you cannot use this method; it throws an IllegalStateException.

 


Accessing the Web Context

The context in which Web components execute is an object that implements the ServletContext interface. You retrieve the Web context with the getServletContext method. The Web context provides methods for accessing:

  • Initialization parameters

  • Resources associated with the Web context

  • Object-valued attributes

  • Logging capabilities

The Web context is used by the Duke's Bookstore filters filters.HitCounterFilter and OrderFilter. The filters store a counter as a context attribute. The counter's access methods are synchronized to prevent incompatible operations by servlets that are running concurrently. A filter retrieves the counter object with the context's getAttribute method. The incremented value of the counter is recorded with the context's log method.

public final class HitCounterFilter implements Filter {
   private FilterConfig filterConfig = null;
   public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain) 
      throws IOException, ServletException {
      ...
      StringWriter sw = new StringWriter();
      PrintWriter writer = new PrintWriter(sw);
      ServletContext context = filterConfig.
         getServletContext();
      Counter counter = (Counter)context.
         getAttribute("hitCounter");
      ...
      writer.println("The number of hits is: " +
         counter.incCounter());
      ...
      context.log(sw.getBuffer().toString());
      ...
   }
}

 


Maintaining Client State

Many apps require a series of requests from a client to be associated with one another. For example, the Duke's Bookstore app saves the state of a user's shopping cart across requests. Web-based apps are responsible for maintaining such state, called a session, because the HTTP protocol is stateless. To support apps that need to maintain state, Java Servlet technology provides an API for managing sessions and allows several mechanisms for implementing sessions.

 

Accessing a Session

Sessions are represented by an HttpSession object. You access a session by calling the getSession method of a request object. This method returns the current session associated with this request, or, if the request does not have a session, it creates one. Since getSession may modify the response header (if cookies are the session tracking mechanism), it needs to be called before you retrieve a PrintWriter or ServletOutputStream.

 

Associating Attributes with a Session

You can associate object-valued attributes with a session by name. Such attributes are accessible by any Web component that belongs to the same Web context and is handling a request that is part of the same session.

The Duke's Bookstore app stores a customer's shopping cart as a session attribute. This allows the shopping cart to be saved between requests and also allows cooperating servlets to access the cart. CatalogServlet adds items to the cart; ShowCartServlet displays, deletes items from, and clears the cart; and CashierServlet retrieves the total cost of the books in the cart.

public class CashierServlet extends HttpServlet { 
   public void doGet (HttpServletRequest request,
      HttpServletResponse response)
      throws ServletException, IOException {

      // Get the user's session and shopping cart
      HttpSession session = request.getSession();
      ShoppingCart cart =
         (ShoppingCart)session.
            getAttribute("cart"); 
      ...
      // Determine the total price of the user's books
      double total = cart.getTotal();

Notifying Objects That Are Associated with a Session

Recall that your app can notify Web context and session listener objects of servlet life-cycle events. You can also notify objects of certain events related to their association with a session, such as the following:

  • When the object is added to or removed from a session. To receive this notification, your object must implement the javax.http.HttpSessionBindingListener interface.

  • When the session to which the object is attached will be passivated or activated. A session will be passivated or activated when it is moved between virtual machines or saved to and restored from persistent storage. To receive this notification, your object must implement the javax.http.HttpSessionActivationListener interface.

 

Session Management

Since there is no way for an HTTP client to signal that it no longer needs a session, each session has an associated timeout so that its resources can be reclaimed. The timeout period can be accessed with a session's [get|set]MaxInactiveInterval methods. You can also set the timeout period in deploytool:

  1. Select the WAR.

  2. Select the General tab.

  3. Enter the timeout period in the Advanced box.

To ensure that an active session is not timed out, you should periodically access the session via service methods because this resets the session's time-to-live counter.

When a particular client interaction is finished, you use the session's invalidate method to invalidate a session on the server side and remove any session data.

The bookstore app's ReceiptServlet is the last servlet to access a client's session, so it has responsibility for invalidating the session:

public class ReceiptServlet extends HttpServlet { 
   public void doPost(HttpServletRequest request,
               HttpServletResponse response) 
               throws ServletException, IOException {
      // Get the user's session and shopping cart
      HttpSession session = request.getSession();
      // Payment received -- invalidate the session
      session.invalidate();
      ...

 

Session Tracking

A Web container can use several methods to associate a session with a user, all of which involve passing an identifier between the client and server. The identifier can be maintained on the client as a cookie or the Web component can include the identifier in every URL that is returned to the client.

If your app makes use of session objects, ensure that session tracking is enabled by having the app rewrite URLs whenever the client turns off cookies. You do this by calling the response's encodeURL(URL) method on all URLs returned by a servlet. This method includes the session ID in the URL only if cookies are disabled; otherwise, it returns the URL unchanged.

The doGet method of ShowCartServlet encodes the three URLs at the bottom of the shopping cart display page as follows:

out.println("<p> &nbsp; <p><strong><a href=\"" +
   response.encodeURL(request.getContextPath() + "/catalog") +
      "\">" + messages.getString("ContinueShopping") +
      "</a> &nbsp; &nbsp; &nbsp;" +
      "<a href=\"" +
   response.encodeURL(request.getContextPath() + "/cashier") +
      "\">" + messages.getString("Checkout") +
      "</a> &nbsp; &nbsp; &nbsp;" +
      "<a href=\"" + 
   response.encodeURL(request.getContextPath() +
      "/showcart?Clear=clear") +
      "\">" + messages.getString("ClearCart") +
      "</a></strong>");

If cookies are turned off, the session is encoded in the Check Out URL as follows:

http://localhost:8080/bookstore1/cashier;
   jsessionid=c0o7fszeb1

If cookies are turned on, the URL is simply

http://localhost:8080/bookstore1/cashier

 


Finalizing a Servlet

When a servlet container determines that a servlet should be removed from service (for example, when a container wants to reclaim memory resources, or when it is being shut down), it calls the destroy method of the Servlet interface. In this method, you release any resources the servlet is using and save any persistent state. The following destroy method releases the database object created in the init method.

public void destroy() {
   bookDB = null;
}

All of a servlet's service methods should be complete when a servlet is removed. The server tries to ensure this completion by calling the destroy method only after all service requests have returned or after a server-specific grace period, whichever comes first.

If your servlet has potentially long-running service requests, use the techniques described below to do the following:

  • Keep track of how many threads are currently running the service method.
  • Provide a clean shutdown by having the destroy method notify long-running threads of the shutdown and wait for them to complete.
  • Have the long-running methods poll periodically to check for shutdown and, if necessary, stop working, clean up, and return.

 

Tracking Service Requests

To track service requests, include in your servlet class a field that counts the number of service methods that are running. The field should have synchronized access methods to increment, decrement, and return its value.

public class ShutdownExample extends HttpServlet {
   private int serviceCounter = 0;
   ...
   //Access methods for serviceCounter
   protected synchronized void enteringServiceMethod() {
      serviceCounter++;
   }
   protected synchronized void leavingServiceMethod() {
      serviceCounter--;
   }
   protected synchronized int numServices() {
      return serviceCounter;
   }
}

The service method should increment the service counter each time the method is entered and should decrement the counter each time the method returns. This is one of the few times that your HttpServlet subclass should override the service method. The new method should call super.service to preserve all of the original service method's functionality:

protected void service(HttpServletRequest req,
               HttpServletResponse resp)
               throws ServletException,IOException {
   enteringServiceMethod();
   try {
      super.service(req, resp);
   } finally {
      leavingServiceMethod();
   }
}

 

Notifying Methods to Shut Down

To ensure a clean shutdown, your destroy method should not release any shared resources until all of the service requests have completed. One part of doing this is to check the service counter. Another part is to notify the long-running methods that it is time to shut down. For this notification, another field is required. The field should have the usual access methods:

public class ShutdownExample extends HttpServlet {
   private boolean shuttingDown;
   ...
   //Access methods for shuttingDown
   protected synchronized void setShuttingDown(boolean flag) {
      shuttingDown = flag;
   }
   protected synchronized boolean isShuttingDown() {
      return shuttingDown;
   }
}

An example of the destroy method using these fields to provide a clean shutdown follows:

public void destroy() {
   /* Check to see whether there are still service methods /*
   /* running, and if there are, tell them to stop. */
   if (numServices() > 0) {
      setShuttingDown(true);
   }

   /* Wait for the service methods to stop. */
   while(numServices() > 0) {
      try {
         Thread.sleep(interval);
      } catch (InterruptedException e) {
      }
   }
} 

 

Creating Polite Long-Running Methods

The final step in providing a clean shutdown is to make any long-running methods behave politely. Methods that might run for a long time should check the value of the field that notifies them of shutdowns and should interrupt their work, if necessary.

public void doPost(...) {
   ...
   for(i = 0; ((i < lotsOfStuffToDo) && 
      !isShuttingDown()); i++) {
      try {
         partOfLongRunningOperation(i);
      } catch (InterruptedException e) {
         ...
      }
   }
}