[ Team LiB ] Previous Section Next Section

Advanced Servlet Programming

We've covered the basic topics of servlet development. The Servlet API covered in earlier sections addresses most of the widely used API, and we also covered how to implement a simple HTTP servlet. We'll now start to discuss some advanced servlet topics with the protocol-independent GenericServlet and then move on topics such as servlet sessions, filters, and event listeners.

Generic Servlets

The javax.servlet package defines a generic base class, GenericServlet, for implementing all servlets irrespective of the underlying transport protocol. Although this servlet form isn't widely used, we'll cover it to complete our discussion of servlet capabilities. GenericServlet can be used to implement any servlet that needs to process, in the same manner, requests from different clients that use different protocols to communicate with WebLogic server. HttpServlet, which is covered in detail throughout the chapter, is an HTTP-specific servlet that extends from GenericServlet and is the most used servlet type.

The GenericServlet class that implements the Servlet and ServletConfig interfaces makes servlet implementation much easier. By these implementations, it takes care of providing simple versions of the life cycle methods init() and destroy(), and handles the methods from ServletConfig. GenericServlet handles the request using the service() method.

Implementing a Simple Generic Servlet

To implement a generic servlet, the developer has to implement the service method. Let's go over a simple example, which generates the factorial of a given number in Listing 14.6.

Listing 14.6 Simple Generic Servlet
public class FactorialServlet extends GenericServlet {
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {
        int number = Integer.parseInt(req.getParameter("NUMBER"));
        res.setContentType("text/html");
        res.writeHeaders();
        ServletOutputStream out = res.getOutputStream();
        String output = "The factorial of " + number +
              "is " + generateFactorial (number);
        out.println(output);
        log(output);
    }

    // This function generates the factorial of a number
    // factorial(n) n! = 1*2*...* (n-1) *n
    // returns the factorial
    public long generateFactorial(int number)
    {
           long factorial=1;
           for ( int j=1; j < n; j++ )
           {
                factorial = factorial*j
           }
           return factorial;
    }
 }

The FactorialServlet is a simple example of GenericServlet. As you can see, it's similar to the first servlet that we saw in the chapter. The only implemented method is the service() method, which processes the given input number and returns the factorial of that number.

Method Summary

The methods that are available for the servlet extending a GenericServlet class are summarized in Table 14.8.

Table 14.8. Generic Servlet Methods

Category

Method

Description

Life Cycle

init() and init(ServletConfig)

Initialization routine that takes no argument or a configuration object that encapsulates initial parameters

 

destroy()

Cleanup routine

Configuration

getInitParameter

Functions to read init parameters from the

Parameters

getInitParameterNames

configuration object

Properties

getServletName

Name of the servlet

 

getServletInfo

Basic information of the servlet such as author, copyright, and so on

 

getServletConfig

Returns the servlet configuration object

 

getServletContext

Returns the servlet context; that is, the servlet's runtime environment

Logging

log(Msg)

log(Msg,Throwable)

Two flavors of log routines that take a message and a Throwable object as arguments; helps logging messages to application log

Request Handler

service()

Handles client request; takes ServletRequest and ServletResponse as arguments

The log method provided in GenericServlet is a rudimentary method that enables servlets to write a simple message to the application log such as the WebLogic Server log. As we said earlier, there are better logging routines in JDK 1.4.

Limitations

Because it is protocol independent, GenericServlet comes with a few limitations:

  • It does not support cookies.

  • HttpSession and Session tracking are not supported.

  • Redirection is not allowed.

As defined earlier, GenericServlet is protocol independent, so it can be used for many protocols, such as FTP, SMTP, and so on. Some of these limitations can be overcome by developing them as part of the servlet, but that makes the developer's job more difficult. The developer has to take care of providing the headers, cookies, session, request types, includes, authentications, and so forth. But these are taken care of for the developer in the case of Http servlets that extend the GenericServlet and implement the HTTP protocol.

Another limitation is that generic servlets are neither called nor managed automatically. This is as opposed to HTTP servlets, which are called by the server when HTTP messages are received. To make our example generic servlet run, we would have to write some kind of container or server to manage it. All these limitations beg the question: What good are generic servlets?

The answer lies in protocols other than HTTP. For example, some products offer SIP servlet servers or containers. (SIP stands for Session Initiation Protocol, and is used to set up Internet telephone calls, for example.) A SIP servlet is implemented from a SIP-specific sub interface of the GenericServlet interface.

For more information about SIP servlet, see http://www.jcp.org/en/jsr/detail?id=116.

Request and Response Streams

For handling binary data in the request and response, the Servlet API defines two classes:

  • javax.servlet.ServletInputStream (extends java.io.InputStream)

  • javax.servlet.ServletOutputStream (extends java.io.OutputStream)

The input stream can be used to read binary data from the user request. As mentioned earlier in the discussion of the ServletRequest, the getInputStream() function returns the servlet input stream object. This abstract class provides container-implemented read() functions. The read() functions read n bytes at a time and the readline() function reads the specified number of characters or stops when the new line is encountered.

The output stream can be used for sending binary data to the user. The output stream can be obtained from the ServletResponse object or one of the inherited classes, such as HttpServletResponse. The container is responsible for implementing the write() methods of the output stream. This class also provides a number of print() and println() functions for each and every data type.

TIP

For higher performance, streamed output is preferred whenever it's available. For example, in the case of servlets, it can be used for dumping the content of a text file to a Web page.


Request and Response Wrappers

The Servlet API introduced convenient wrapper classes for the ServletRequest and ServletResponse interfaces and equivalent implementations for the HTTP Servlet API. These classes, which are based on the Decorator pattern, are

  • ServletRequestWrapper and HttpServletRequestWrapper

  • ServletResponseWrapper and HttpServletResponseWrapper

According to Sun Microsystems, "These wrapper classes provide a convenient implementation of the request and response respectively and help users to adapt the request to a servlet and response from a servlet." The wrappers implement the same functions that are defined as part of the corresponding request and response objects. For example, HttpServletRequestWrapper stands for HttpServletRequest, and HttpServletResponseWrapper for HttpServletResponse. In other words, you can emulate a request event on the server using ServletRequestWrapper and capture the produced output with ServletResponseWrapper. Developers can also use these where a servlet might be used outside the container boundaries.

One of the most common uses for employing wrappers is to redirect output to a different output stream. One of the projects that effectively uses this servlet feature is the Apache project JetSpeed. JetSpeed uses wrappers to redirect output to an alternative output stream using the EcsServletResponse object. We'll look at an example later in the chapter during our discussion of the servlet filtering. Figure 14.8 describes the wrapper class hierarchy in the Servlet API specification and also includes simple response wrapper.

Figure 14.8. Class hierarchy—wrappers.

graphics/14fig08.gif

Handling Sessions

Sessions, by the simplest of definitions, enable you to track user's footprints over a Web site composed of many HTML pages or servlets. Sessions are a necessary requirement for all Web applications to be able to track the pages visited by the user and to make a Web site effective and usable. Over the years, many approaches have been adopted to implement sessions in Web applications, but most of them are cumbersome, difficult to implement, and nonstandard. One of the most common approaches was to embed state information as hidden fields in the page or to add it as strings to the URL. Although this approach worked, one main drawback was that the user information was lost when the user navigates out of the Web application to a different site and then comes back to the same site. There was no mechanism for passing information from one session to another.

Sessions and HTTP

By design, HTTP is a stateless protocol and every Web request could potentially open a new connection with the Web Server. In general, Web servers do not maintain state information about the users by default. This is true for a server such as WebLogic Server, even if it can maintain durable connections. In most e-commerce applications like online books, groceries, and electronics, the application has to be aware of the items that a customer has chosen between, a logon and logout in multiple visits. Sessions play a key role in implementing these Web applications. The server must identify the shopping cart of the customer and the things that he added to the cart during his navigation through the many pages of the application.

Sun's Servlet specification defines the HttpSession interface, which provides a standard set of approaches for servlet developers and containers such as WebLogic Server alike to map a user's session without tying them to a particular approach. The HttpSession object is capable of storing user details on the Web server across multiple requests, and provides a standard way for implementing sessions. It makes the user code more maintainable by hiding the complexities of session management. The HttpSession object can also be used to exchange user session details between different servlets. Now we'll have a detailed look at how to use the HttpSession object in servlets for session management with WebLogic Server.

Session Tracking Approaches

In this section, let's look at the different approaches available for session management in servlets.

Cookies

Cookies were introduced by Netscape to store user information associated with every Web Server. A cookie is defined as a text-only string that is stored temporarily or permanently on the client; that is, the Web browser. The capability to store information in this fashion can be leveraged to store a session identifier. The servlet container is responsible for sending the HTTP cookie to the client in order for the client to use it on every following request, identifying the request as participating in that session. By definition of the Sun Servlet specification, "the name of the session tracking cookie must be JSESSIONID." WebLogic Server uses this as the default value when unset, but provides a configuration parameter as part of the session-descriptor tag to change the name.

A simple example in which a cookie can be used is in the case of identifying a registered customer. After a user is successfully logged in, customer-related information can be stored in a cookie and sent back to the client. The following code snippet demonstrates the use of cookies to store a customer name:


void doPost(HttpSessionRequest req, HttpSessionResponse resp) {
...
String customerName=findCustomerName(accountId);
Cookie sessionCookie = new Cookie("CustomerName", customerName);
resp.addCookie(sessionCookie);

After this request is processed, the response contains the cookie CustomerName, which is saved in the browser's cache. When the customer returns to the Web application at a later time, this information, which might be used by the processing component, is sent back to the server.

URL-Rewriting

This is a complementary approach to session management, wherein the session ID is embedded into the links of the Web page that's sent back to the client. When the user navigates to these pages using the hyperlinks, the session ID is available to WebLogic Server for extraction as an HttpSession. Browsers have the capability to turn off cookies and thereby make session management using cookies impossible. For this reason, this approach should be always implemented so that the server can use this for session management if cookies are disabled. The only major disadvantage of this approach is that when the user comes to the site by clicking a link on an external site or by using a bookmark, the session information is not available at this time for the server.

There are two ways in which URL rewriting can be accomplished. When the servlet returns URLs to the browser, you can use HttpServletResponse.encodeURL(). When it redirects them, we have to use HttpServletResponse.encodeRedirectURL(). We'll look at both ways with the help of simple examples.

In an online pizza ordering system, you have a servlet with the following line:


out.println("<a href=\"/pizzeria/pizzalist\">pizzacatalog<a>");

The URL that's sent back to the client can be encoded using encodeURL before sending the URL to the output stream.


out.println("<a href=\"");
out.println(response.encodeURL ("/pizzaria/pizzalist"));
out.println("\">pizzacatalog</a>");

But suppose that you have to redirect the user to a local pizzeria's home page depending on the ZIP code of the customer. The code would look like the following:


response.sendRedirect("http://www.pizzeria.com/losangeles/pizzalist");

In this case, URL rewriting is accomplished using the encodeRedirectURL method. The redirect code will look like the following with URL rewriting enabled:



response.sendRedirect(response.encodeRedirectURL ("http://www.pizzeria.com/losangeles
graphics/ccc.gif/pizzalist));

On the first request, the URL is encoded even if cookies are turned on. But it will stop encoding URL once it detects that the browser supports cookies. HttpServletRequest provides the utility method isRequestedSessionIdFromCookie(), which enables servlets to determine whether or not a given session ID was received from a cookie by checking the Boolean value returned. This helps the servlet to respond appropriately. Also, when a URL is rewritten along with encoding the parameters, it appends the session ID to the URL with the session ID preceded by a semicolon. For example, the URL


"SimpleServlet?INPUT_STRING=Hello World"

will be rewritten as


"SimpleServlet?INPUT_STRING=Hello+World:JSESSIONID=12343443"
Session Configuration

By default, WebLogic Server is enabled for session tracking. You don't need to set any parameters for configuring cookies and URL rewriting. But when some session properties must be modified according to an application specification, WebLogic Server controls the HTTP session configuration, including cookies and URL rewriting, by using the Session-Descriptor element tag in the WebLogic-specific deployment descriptor file, weblogic.xml. The following XML snippet sets the expiry time (in seconds) of the session ID cookie:


<session-descriptor>
  <session-param>
     <param-name>
       CookieMaxAgeSecs
     </param-name>
     <param-value>
      1000000
     </param-value>
  </session-param>
</session-descriptor>

Table 14.9 defines some of the valid session-param values related to the cookie-based session management in WebLogic Server.

Table 14.9. Session Identifier Cookie-Related Session Parameters

Type

Description

CookieName

User-defined name for the session ID cookie. Default value is JSESSIONID.

CookieDomain

Name of the domain for which the cookie is valid. Can only be the server and domain (for example, www.example.org) or the domain (example.org) and nothing else.

CookieMaxAgeSecs

Life of the cookie in seconds. Default is –1, which indicates that the cookies expires when the browser closes (in-memory cookie).

CookiesEnabled

Enables cookies by default, but can be turned off by setting this attribute to false.

CookiePath

Virtual directory where the cookie will be stored and retrieved. This allows multiple JSESSIONID cookies that point to different applications inside the same domain.

CookieComment

Useful tag to add comments to the cookie file.

Secure Sessions

Secure Sockets Layer (SSL) provides an effective mechanism for session management in HTTPS-based request-response. The mechanism allows a servlet engine such as WebLogic Server to identify the client request as being part of a continuing session.

But one important consideration when implementing session is that cookies are not totally secure. You should avoid storing important data in cookies. The only protection that can be offered to cookies is by forcing the browser to send them only if HTTPS is enabled. This can be achieved by using the setSecure() method on the Cookie class. But this cookie-secure parameter applies only when the cookie's originating server used a secure protocol to set the cookie's value.

Hidden Form Fields

In this approach, a hidden input field is used to store the session information. But this approach is limited in use because it can be used only when every page in a Web application is generated dynamically.

Using the HttpSession Object

The core interface of the session management implementation in servlets is the HttpSession interface. Cookies and URL rewriting form the foundation for this high-level interface. For most servers, cookies are the automatic option for propagating the session data to the client and when the browser turns off cookies, URL rewriting is used instead.

Creating the Session Object

The HttpSession object is accessible for all servlets through the request object, which is passed as an argument to the service() and the doXXX() methods. The session can be obtained from the request object as given here:


HttpSession session = req.getSession(true);

The boolean argument to the getSession() method determines whether to create a new session if one does not exist for the specific client. In this earlier code, getSession returns the existing session (if one exists) or creates a new one. Alternatively, when the argument is false, getSession returns null if the session does not exist.

The HttpSession interface provides a rudimentary isNew() method that returns true if the server created the session in the getSession method, or false if the client sent this session in the request.

NOTE

In authenticated sites, it's advisable to use getSession(false) in all servlets. This preserves the existing session and makes sure that no one can enter any part of the site unauthenticated. The exception is your login servlet, where you would use getSession(true) if the credentials are good. Even in nonauthenticated sites, making a call to getSession at the top of every servlet makes sure that the session isn't lost.


Using the Session Object

The Session object created in the previous step lives in WebLogic Server during the lifetime the session, and is available for the servlet developer to add attributes to and remove attributes from. The collected information is associated with a single client and is available through the HttpSession object on subsequent visits of the client to the Web application.

HttpSession API

The HttpSession interface provides a number of methods for manipulating the client data inside the session object, as well as methods to read the properties of a given session such as the creation time, modified time, and so on. Attributes in the session object are manipulated with the following functions:

  • getAttribute()— This function takes a String identifier that identifies the attribute as an argument and returns the corresponding value.

  • getAttributeNames()— Returns an enumeration of names of the attributes embedded in the session object. The following code snippet prints the attribute names inside a session object along with the values:

    
    
    HttpSession session = req.getSession(true);
    Enumeration ee = session.getAttributeNames();
    String attrName,value;
    While (ee.hasMoreElements() )
    {
          attrName = ee.nextElement();
          value = (String) session.getAttribute(attrName);
          System.out.println("Attribute:"+attrName+"="+str);
    }
    
  • setAttribute— Sets or overwrites an attribute along with a corresponding value.

  • removeAttribute— Removes the attribute from the session object.

The following code snippet extracted from a typical LoginServlet of an e-commerce Web application demonstrates the earlier functions for setting attributes into the user session:


HttpSession session = req.getSession(true);
session.setAttribute("CustomerId", dto.getCustomerId());
session.setAttribute("CustomerName", dto.getFirstName() + " " + dto.getLastName());
if (dto.getIsBuyer().booleanValue())
    session.setAttribute("IsBuyer", "true");
if (dto.getIsSeller().booleanValue())
    session.setAttribute("IsSeller", "true")

As mentioned earlier, the HttpSession interface provides a set of useful methods as listed in Table 14.10.

Table 14.10. HttpSession Interface

Type

Description

getCreationTime

Returns the creation time in milliseconds.

getLastAccessedTime()

Returns the last accessed time in milliseconds as a Long value.

getId()

Returns the unique identifier for the session. This is the same as the value stored in the JSESSIONID cookie and passed in for URL rewriting.

In an application, where the user-related sensitive data is stored in the session, a logout operation is a must. The HttpSession interface provides a function to invalidate a client session. The invalidate() function, which is called on the user session object, logs the user out. In doing so, it clears all the user-related session data that was collected during the course of the client session, which might have spanned multiple visits to the Web application. The following code snippet is simple example of a logout:


HttpSession session = req.getSession(false);
if (session != null) {
      session.invalidate();
}

When the same session is referred to after the invalidate call, the servlet container throws an IllegalStateException. This exception can be used to send the user back to the logon screen. The invalidate() call, although it makes the client session invalid, does not remove the user information from the server context. For the purpose of handling sessions across multiple Web applications in the same server, WebLogic Server provides a special set of authentication methods that log out the user from multiple Web applications in the same server context as part of the weblogic.security.servlet.ServletAuthentication object. This object is a WebLogic helper class and is used for implementing form-based authentication. It also helps to simplify the program-based authentication in servlets.

  • invalidateAll— Takes the servlet request as an argument and invalidates all the sessions for the current user.

  • logout()— Takes the servlet request as the argument and logs out from the current Web app by removing the user authentication data in the session and leaving rest of the session data intact.

  • done()— Similar to the logout call.

  • killCookie()— Kills the active session identification cookie, leaving the session on the server to time out.

According to the Sun specification, a servlet container such as WebLogic Server should take care of making the HttpSession object unique at the Web application (servlet context) level.

Session Timeouts

Session timeouts play an important role in cleaning up client sessions that stay up for a long time because the client does not come back to invalidate the session.

WebLogic Server provides the following server configuration options for setting the timeout values for sessions. The timeout can be configured using the different configuration elements listed here. These tags are in both Web application deployment descriptors.

  • <session-config> <session-timeout> tag (web.xml)

  • <session-descriptor> TimeoutSecs parameter (weblogic.xml)

The Web deployment descriptor (web.xml) <session-timeout> element inside the <session-config> element indicates, in number of minutes, the client stays alive. This setting overrides all other timeout settings that can be set elsewhere (for example, the <session-descriptor> timeout parameter) except when it is set to -2. When set to –2, it indicates to the server to use the session-descriptor value instead of the session-timeout value. When set to -1, sessions do not time out and the value set in the <session-descriptor> element is also ignored. The default value is -2. The following XML snippet makes sure that sessions do not time out at all:


<session-config>
   <session-timeout>
        -1
   </session-timeout>
</session-config>

The session-descriptor element TimeoutSecs session parameter also can be used to set the session timeout values, following the rules explained earlier. The value represented by TimeoutSecs is the number of seconds that WebLogic Server will wait before timing out of a session. The default value is 3600. The following XML snippet sets the timeout to 10000 seconds:


<session-descriptor>
  <session-param>
     <param-name>
       TimeoutSecs
     </param-name>
     <param-value>
      10000
     </param-value>
  </session-param>
</session-descriptor>

The HttpSession API provides the setMaxInactiveInterval() function, which overrides the default timeout value set by the servlet container. The getMaxInactiveInterval() function call returns the current value of the session timeout. The default value of the session timeout is usually set to –1, which indicates that the session never expires.

Session Persistence

WebLogic Server offers a facility to persist sessions in a number of different ways. Session persistence offers quite a few advantages:

  • By providing a facility for storing sessions in a permanent store such as a file or a database, session persistence offers increased availability of sessions because they're protected against server crashes.

    CAUTION

    Even if a session is persisted, it doesn't mean that the session will not be lost. There is a possibility that the server could become unavailable as the session is persisted. The client will see relevant error messages when this situation happens. But the session can be retrieved only by one of the following methods: the user doesn't close his browser (else the cookie could be lost); the server comes up before the session expires; or the client makes a request to the server after it comes back up but before the session expires.


  • Enhanced load balancing and optimized performance by enabling caching for sessions in memory, a file, or a database.

Sessions can be persisted in any of the following ways:

  • Memory— This is single-server nonreplication persistence in which the session data in stored in the single WebLogic Server instance; therefore, it does not provide failover.

  • File— In this mode, sessions are persisted to a file to a directory specified in the server configuration.

  • Cookies— This option, which was explained earlier, is limited in the kind of data that can be stored as session data. Cookies only provide a facility to store strings. This is also dependent on the browser's setting for cookies because browsers have the option to turn off cookies. This makes their behavior highly unpredictable and inadequate for most systems. This approach should be avoided.

  • Database— In this case, sessions are persisted to a database table using JDBC. This option offers the highest reliability for session persistence, but it comes at a cost of performance overhead.

  • In-memory replication— This option can be used in WebLogic clusters.

The session state configuration is managed by the <session-descriptor> element (briefly discussed in the cookies section as well as in the section on session timeouts) along with some of the valid properties in Table 14.8. The main property governing the type of session persistence is PersistentStoreType, which is a session parameter. This property can be set to one of the following values: memory, file, jdbc, cookie, and replicated.

There are associated properties, which are required apart from PersistentStoreType depending on the type of session persistence. Table 14.11 summarizes the list of properties that are part of the session-descriptor element in weblogic.xml.

Table 14.11. Session Persistence

Property

Store Type

Description

PersistentStoreDir

file

Specifies the directory where the WebLogic sessions will be stored.

PersistentStorePool

jdbc

Specifies the JDBC connection pool to be used for managing JDBC connections. For a detailed discussion about the JDBC connection pool, refer to "Managing Database Connectivity with JDBC."

JDBConnectionTimeoutSecs

jdbc

Specifies the time in seconds that WebLogic Server waits for a JDBC connection.

PersistentStoreCookieName

cookie

Name of the cookie for cookie-based session persistence. Default is WLCOOKIE.

InvalidationIntervalSecs

60

This value tells WebLogic Server how long it has to wait to do the housecleaning checks for timed-out and invalid sessions and delete them. The range for this element is from 1 second to 1 week (604800 seconds).

SwapIntervalSecs

10

This value indicates the WebLogic Server sleep time between transferring the least recently used sessions from the cache to the persistent store.

CacheSize

1024

Helps to control the number of cached sessions that can be active in memory at a given time.

Some of the additional things that the WebLogic administrator has to remember are listed here:

  • When the store type is memory, a session lives only for the duration of the life of the server. That is, when the server goes down, the session state is lost.

  • When the store type is file in a WebLogic clustered environment, PersistentStoreDir should point to a shared directory between the servers.

  • When the store type is jdbc, the user needs to create wl_servlet_sessions table with read/write permissions for the users of the JDBC connection pool writing the session. The definition of the wl_servlet_sessions table with field names and type can be viewed at the BEA site http://e-docs.bea.com/wls/docs81/webapp/sessions.html in the "Using Database for Persistent Storage" subsection.

  • Cookie-based session persistence offers a range of benefits. Because the session data is stored in the client, clustering and failover logic are not required. It also makes the life of the server independent of the session persistence; that is, servers can be restarted without losing session data. But cookie-based persistence is suitable only when the sessions contain very little data. There are many limitations to using cookies.

NOTE

At this time, we won't look at the clustering option for session persistence. It's the most efficient option of session persistence for WebLogic clusters, but that discussion is deferred to the chapter on WebLogic clusters (Chapter 36).


A Complete Session Example

We've so far examined, in detail, the HttpSession API and how to implement servlets. In this section, we convert the SimpleServlet we developed in an earlier section to be session aware. The modified servlet will keep track of the number of times that a client visits the Web site for doing the requested function. Listing 14.7 lists only the modified doGet() method with sessions. All the other methods remain the same as defined in Listing 14.5.

Listing 14.7 Simple Servlet with Sessions
// Handles the GET request
1     public void doGet(HttpServletRequest req, HttpServletResponse res)
2        throws IOException
3    {

4         String convertedString,inputString;
5        //Get the session object
6       HttpSession session = req.getSession(true);

7        //read the request parameter INPUT_STRING
8       // If the input String is null
9       // it returns "hello world" In uppercase

10        if ((inputString = req.getParameter("INPUT_STRING"))
11            != null) {
12            convertedString = inputString.toUpperCase();
13        }
14        else {
15            convertedString =defaultString ;
16        }
17      // Get the visit count value
18       Long visitCount= (Long) session.getAttribute("visit_Count");
19       if (visitCount==null)
20          visitCount = new Long(1);
21       else
22           visitCount = new Long(visitCount.longValue() + 1);
23       session.setAttribute("visit_Count", visitCount);

24        // Set the content type first
25        res.setContentType("text/html");

26        // get the PrintWriter
27        PrintWriter out = res.getWriter();

28        out.println("<html><head><title>SimpleServlet - Session Enabled</title></head>");
29        out.println("<body>");
30        out.println("<h1>");

31        out.println("The input string upper case(d):" + convertedString );
32        out.println("</h1>");
33        out.println("<br><br>");
34        out.println("Number of Conversions Performed <b>" + visitCount + "</b> times.<p>");
35        out.println("<h2>Session Data:</h2>
36        out.println("Is this the first time to the site?: " + session.isNew());
37        out.println("<br>Client Identifier: " + session.getId());
38        out.println("<br>First Conversion using the Servlet: " + session.getCreationTime());
39        out.println("<br>Last Conversion Time: " + session.getLastAccessedTime());

40        out.println("</h1></body></html>");
41    }

The session object is created in line 6. If a session does not exist, getSession(true) returns a new session. If it is a returning client, it returns the client's session corresponding to its ID. After creating a new session or retrieving an old session, attributes can be read using getAttribute (line 18) and modified using setAttribute (line 23). The SimpleServlet output is also modified to include the session-related information as demonstrated by lines 28 through 37.

TIP

It's a good practice to create the session at the beginning of the method to make it evident that it participates in a session.


When this servlet is invoked for the first time, you should see output that looks like the following:



Number of Conversions Performed 1 times.
Session Data:
Is this the first time to the site?: true
Client Identifier:
graphics/ccc.gif 2Vg5oH677V1yA9b1fX2vNv76K9Q33hl9Z7SXiIcc2h8azDKQKYJc!-860518150!1050009849936
First Conversion using the Servlet: 1050009849936
Last Conversion Time: 1050009849936

The session is not created in subsequent calls, as you can see from the value of First conversion using the Servlet in the following output. All other things have a new value for the second invocation.



Number of Conversions Performed 2 times.
Session Data:
Is this the first time to the site?: false
Client Identifier:
graphics/ccc.gif 2Vg5oH677V1yA9b1fX2vNv76K9Q33hl9Z7SXiIcc2h8azDKQKYJc!-860518150!1050009849936
First Conversion using the Servlet: 1050009849936
Last Conversion Time: 1050009849916
Application Events and Listeners

Application events such as session-related events and servlet context–related events were added to Sun's Servlet specifications in version 2.3. These events facilitate notifications when there is a change in the servlet context or in the HttpSession object. The application server is responsible for providing the infrastructure for handling such events with the help of listener classes. The listener classes can be configured using the Web application deployment descriptor, and they respond to the various events that happen during the life of the session object or the servlet context.

The servlet context events are generated during life cycle operations, such as Web application deployment or when it is undeployed, and also during attribute manipulation, including adding, removing, and replacing them. Similarly, HttpSession events are generated during life cycle operations such as activation or passivation of session state as well as during HTTP session attribute maintenance (adding, deleting, and updating an attribute) as shown in Figure 14.9.

Figure 14.9. HttpSession life cycle events.

graphics/14fig09.gif

The life cycle events trigger, especially the servlet context events, could probably be used to initialize or clean up resources such as databases, legacy connections, and so on because the events indicate when the application is deployed or undeployed. On the other hand, HTTP session events can be used to monitor the session state along with its attributes.

Using the Listener API

The Servlet specification defines different sets of interfaces for managing the servlet context–related events and the HTTP session events. The interfaces are defined in the javax.servlet and javax.servlet.http packages, respectively.

ServletContextListener

The ServletContextListener interface manages the life cycle–related events of the servlet context. When a Web application is initialized or created, the contextInitialized() method of the listener is invoked and contextDestroyed() is called when the application prepares to shut down.

ServletContextAttributesListener

This listener interface is used to implement a listener to handle events when the attributes are added(), deleted(), or replaced() in the servlet context.

HttpSessionListener

The HttpSessionListener interface manages the life cycle–related events of the HTTP session state. When the HTTP session is activated or created, the sessionCreated() method of the listener is invoked and sessionDestroyed() is called when the HTTP session is to be invalidated.

HttpSessionAttributeListener

This listener interface is used to implement a listener to handle events when attributes are added(), deleted(), or replaced() in the HTTP session object. For example:


MyAttrListner implements HttpSessionAttributeListener{
// Implmentation methods
}

This class reacts to any attribute change that occurs because of either of the following function calls on a session object:


Session.setAttribute("object",AnySessionObj);

or


Session.getAttribute("object");

Apart from the interfaces listed here, the Servlet specification also defines HttpSessionActivationListener and HttpSessionBindingListener for attributes that are bound to a session.

HttpSessionBindingListener

The class implementing this interface is notified with an HttpBindingEvent object when an attribute is explicitly bound to a session or unbound from a session. The HttpBindingEvent object provides methods to get the attribute name and value that changed. For example:


MyBindingObject implements HttpSessionListener{
// Implmentation methods
}

In this case, the implementation methods of the class react when it is bound or unbound to a session with one of the following calls on the session object:


Session.setAttribute("object",MyBindingObject);

or


Session.removeAttribute("object");

At this point, it is good to note the differences between this interface and the HttpSessionAttributeListener interface. They seem to do the same thing, but when a class implements HttpSessionBindingListener, it works only for binding methods (add/remove). On the other hand, when a class implements HttpSessionAttributeListener, it has the capability to react to any attribute change (add/remove/replace).

HttpSessionActivationListener

This interface is for objects that are bound to a session as attributes. The container is responsible for notifying the attributes that implement this interface when a session to which it is bound is activated or passivated. Passivation is the process by which sessions are stored in a persistent store for later use in persistent sessions or might be transferred to another server for failover in a WebLogic cluster scenario. Activation is the reverse process in which a session is brought back into memory for use with the associated client. EJBs use a similar principle for the different types of Enterprise JavaBeans; this is explained in great detail in the chapter on EJBs. This interface provides the event tracking methods sessionDidActivate() and sessionWillPassivate() that take an HttpSessionEvent object as an argument. The HttpSessionEvent represents the changes to the session inside a Web application. This object provides a useful getSession method to return the current session.

Configuring Listeners

The listeners are configured in the Web application deployment descriptor web.xml using the <listener> element. The following XML snippet defines two listeners that implement the HttpSessionListener and HttpSessionAttributeListener interfaces explained earlier:


<web-app>
   <listener>
      <listener-class>servlets.simple.SimpleSessionListener</listener-class>
   </listener>
   <listener>
      <listener-class>servlets.simple.SimpleSessionAttributeListener</listener-class>
   </listener>

</web-app>

WebLogic Server allows multiple listeners for the same event. The listeners are invoked in the order in which they're defined in the web.xml file. The only exception to that rule is shutdown events: invocation is in reverse order.

Implementing Simple Http Listeners

In the previous section, we extended the SimpleServlet implementation to include sessions to store the user's footprints along with access history (visit count). Now, in this section, we look at an alternative implementation of tracking counts using the session listener and session attribute listeners.

Simple HTTP Session Listener

Listing 14.8 implements a simple HTTP session listener that, with the help of life cycle events, tracks a client visits to a Web application and prints the output to the WebLogic Server Console. This can be used in conjunction with the session example we saw earlier in the chapter.

Listing 14.8 Simple HTTP Session Listener
package wlsunleashed.servlets;

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

/*
*  Simple Session Listener for maintaining the user's footprints
*/

public class SimpleSessionListener implements HttpSessionListener {

  HttpSession session = null;

  // default constructor
  public SimpleSessionListener() {
  }

  // Life cycle event invoked when the session is created
  // Adds a counter to the session to tract the user's visit to the site
  public void sessionCreated(HttpSessionEvent evt){
    session = evt.getSession();
    Long visitCount = new Long(1);
    System.out.println("Session Created");
    session.setAttribute("visit_Count", visitCount);
  }

  // Life cycle event invoked when the session is destroyed
  // Prints the total number of visits to the server by the server
  public void sessionDestroyed(HttpSessionEvent evt){
    session = evt.getSession();
   System.out.println("Session Destroyed");
    Long visitCount = (Long) session.getAttribute("visit_Count");
    if (visitCount==null)
        visitCount = new Long(1);
    else
        visitCount = new Long(visitCount.longValue() + 1);
    System.out.println("Total Number of Hits="+visitCount.longValue());  }
}
Simple HTTP Session Attribute Listener

The session attribute listener can be used with the simple session example that we saw earlier (SimpleServletWithSession) to track the exact timing of client visits to the servlet and to print the output to the WebLogic Server Console. Listing 14.9 provides a simple implementation of an attribute listener.

Listing 14.9 Simple HTTP Session Attribute Listener
package wlsunleashed.servlets;

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

/*
*  Simple Session Attribute Listener for maintaining the user's
*  access time of the servlet
*/

public class SimpleSessionAttributeListener implements
 HttpSessionAttributeListener {

  public SimpleSessionAttributeListener() {
  }
  /**
   * Invoked when attribute is removed using setAttribute for the first time
   */

   // Tracks the user's access to the servlet using "visit_Count"
   public void attributeAdded(HttpSessionBindingEvent evt) {
   if ( evt.getName().equals("visit_Count")){
       System.out.print("Session(Attribute Added): " + evt.getSession().getId() );
       System.out.println(" Time accessed:"+ new Date());
   }
  }

  /**
   * Invoked when attribute is removed using removeAttribute
   */
  public void attributeRemoved(HttpSessionBindingEvent evt) {
   System.out.print("Attribute removed: " + evt.getName() +"="+ evt.getValue());
   System.out.println(" in session : " + evt.getSession().getId() );
  }

  /**
   *  // Tracks the user's access to the servlet using "visit_Count"
   */
  public void attributeReplaced(HttpSessionBindingEvent evt) {
   if ( evt.getName().equals("visit_Count")){
       System.out.print("Session(Attribute Replaced): " + evt.getSession().getId() );
       System.out.println(" Time accessed:"+ new Date());
   }
  }
}

Similarly, servlet context listeners (implemented using ServletContextListener) and their corresponding attribute listeners can be used both to manipulate resources such as database connections and when servlets use the Web application context to share data or resources between them.

Simple HTTP Listener Configuration

To configure the listeners we've written here, we need to add event listener declarations using the <listener> element to the Web application deployment descriptor (web.xml). You can define multiple listeners and WebLogic Server will invoke the event listener classes in the order in which they appear in the deployment descriptor.


<listener>
    <listener-class>wlsunleashed.servlets.SimpleSessionListener</listener-class>
</listener>
<listener>
    <listener-class>wlsunleashed.servlets.SimpleSessionAttributeListener</listener-class>
</listener>

When you invoke the simple session servlet after you configure the attribute listener, the following messages will appear on the WebLogic Console when the attributes change:


Session(Attribute Added): 2V298nN1Q7TDWmMFUmpxb3k8ZWmC00joB7ZBMdCPHV3biRQ8DN5W!-
444702951!1050015293694
Time accessed:Thu Apr 10 15:54:53 PDT 2003
Session(Attribute Replaced): 2V298nN1Q7TDWmMFUmpxb3k8ZWmC00joB7ZBMdCPHV3biRQ8DN5
W!-444702951!1050015293694
Time accessed:Thu Apr 10 15:54:54 PDT 2003

Filters

The filters concept was introduced in Servlet specification 2.3 to give Web component developers a facility to transform requests and modify the servlet response. Filters are like listeners in that they cannot be invoked directly by a Web client. They are like pre- and post-processors for the request and response, respectively. Filters can modify both the header and content of the request/response. The filter component can work only on an existing request or a response; that is, it cannot create a request or response by itself. This capability provides an advantage for manipulating any associated Web application resource, namely, servlets, JSP, and so on. A single filter can be used to manipulate multiple Web resources' requests and responses.

Figure 14.10 describes the main tasks in a filter and some of the uses of a filter. On an incoming request/response from a Web component such as a servlet, the configured filter extracts the request or response. The filter then applies the rules defined in it on the request/response. The rules applied modify the request/response content, the header information, or both. The rules are applied using a customized version of the request or the response object. As shown in Figure 14.10, filters can integrate with external entities such as databases, files for operations like logging, authentication, caching, and so on. Filters provide the capability to manipulate the request/response before it reaches the Web component/client. Additionally, a given request can be passed through a chain of filters, each implementing a different function such as auditing, logging, encryption, and so forth.

Figure 14.10. Filter tasks.

graphics/14fig10.gif

Filters can be used to implement some of the following features:

  • Authentication component in Web applications

  • Log requests and responses

  • Transform XML data using XSLT for presentation

  • Implement security features such as encryption

  • Cache and audit requests and responses

Additionally, the Sun specification suggests that filters can be used for data compression, image conversion, and to trigger external events such as database events.

Filter API

The heart of the filtering concept is the Filter interface defined in the javax.servlet package. This interface defines the methods for life cycle operations such as initialization and termination as well as for filtering operations. A user-defined filter should implement the following functions:

  • doFilter()— This method is invoked by the container, implements the filtering logic, and is called every time a client request passes through a filter chain.

  • init()— An initialization routine that is called by the container before the filter is first used.

  • destroy()— A cleanup routine that's called by the container before a filter is destroyed.

The init() method should be successfully executed before the filter can be used. The destroy() method can be used to free up resources such as threads, memory, and so forth and can be used to store any persistent state to a permanent store such as a file or a database. The finalize clause is also an alternative for the destroy method. But destroy and finalize should not be heavily relied on because there is no assurance that they will be invoked all the time (for example, in server crashes). So, it is not prudent to rely on this method to persist important information.

The Filter API definition includes a configuration interface (FilterConfig) and a FilterChain interface to link multiple filters for processing the same client request.

FilterConfig Interface

This configuration object is used to pass any user-specified configuration parameters of the filter to the init() method. Similar to other configuration objects, such as ServletConfig, this interface defines getInitParameter() and getInitParameterNames() methods for returning the value of a parameter and an enumeration of parameter names, respectively. This interface also provides the utility method getFilterName(), which returns the name of the filter as defined in the deployment descriptor. The configuration object also provides a method (getServletContext()) to return the current servlet context, thereby giving the filters a handle to the Web application context and their environment settings.

FilterChain

This object is passed as an argument to the doFilter method along with the request and response. The FilterChain object passes the request and response to the next entity in the chain. If the calling filter is the last filter in the chain, FilterChain causes the service() method of the servlet to be invoked.

Earlier in this section, we pointed out that filters could be used to perform XSLT transformation on the servlet response before it's presented to the client. Multiple filters could be used to do the transformation depending on the client's capability to present them and some of the filters might not be applied to all client types. A filter chain can be used to configure the set of filters that's to be invoked and the order in which they have to be invoked.

A Simple Filter

Now we'll look at implementing a simple filter that logs the remote host accessing your application. To keep it simple, the filter logs the value to the WebLogic Server Console. Listing 14.10 is a simple sniffer filter implementation.

Listing 14.10 Simple Filter
1 package wlsunleashed.servlets.;
2 import java.io.*;
3 import java.util.*;
4 import javax.servlet.*;
5 import javax.servlet.http.*;

6 public final class SniffFilter implements Filter {
7    private FilterConfig filterConfig = null;

8    public void init(FilterConfig filterConfig) throws ServletException {
9        this.filterConfig = filterConfig;
10    }

11    public void destroy() {
12        this.filterConfig = null;
13    }

14 public void doFilter(ServletRequest request, ServletResponse response,
15      FilterChain chain) throws IOException, ServletException {

16     if (filterConfig == null)
17        return;

18     HttpServletRequest req = (HttpServletRequest)request;

19     Enumeration e = req.getHeaderNames();
20     while(e.hasMoreElements()) {
21       String headerName =(String) e.nextElement();
22       System.out.println(headerName+"="+req.getHeader(headerName));
23    }

24     System.out.println("RemoteHost = "+req.getRemoteHost());

25     chain.doFilter(request, response);
26   }
27 }

In line 24 of Listing 14.10, the simple sniffer filter writes the remote hostname accessing this Web application to the console. Because this is the only configured filter in the chain, it calls chain.doFilter in line 25, which invokes the servlet instance for which the request was intended.

Simple Filter Configuration

We have to configure the filters using the Web application deployment descriptor (web.xml). The following XML snippet provides the configuration of the simple filter discussed earlier:


<web-app>
...
<filter>
    <filter-name>sniffFilter</filter-name>
   <filter-class>wlsunleashed.servlets.SniffFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>sniffFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

...
</web-app>

The configuration defines the logical name of the filter for the simple filter defined earlier along with the filter mapping that defines the URL pattern for which the filter has been defined.

Parameterized Filters

As we've seen, the FilterConfig() interface is used to send initialization parameters, if any, to the filter. The initialization parameters are defined in the web.xml file along with the filter element as a sub element. Let's define a default, which will be printed during initialization of the filter.


<filter>
    <filter-name>SniffFilter</filter-name>
    <filter-class>wlsunleashed.servlets.SniffFilter</filter-class>
               <init-param>
                     <param-name>welcomeMessage</param-name>
                     <param-value>A Simple Filter Example</param-name>
               </init-param>
</filter>

The welcomeMessage parameter is accessible to the filter through the FilterConfig.getInitParameter() function.

Using Wrappers

One of the most important uses of a filter is the capability to customize a servlet response/request to perform the defined filtering task and to enable overriding of the default behavior of the response. This design enables developers to exercise more control over the responses sent back to the client and to a target Web resource in the filter chain. Filters can be used to add attributes, modify them in a response, and even add attributes to it before it reaches the client.

The filter should modify the response before it's flushed by the processing servlet to the client. The most common approach is to pass a wrapped stream to the processing servlet.

The wrapped stream takes care of preventing the flushing of the response to the original response stream and allows the defined filters to modify the response.

The wrapped or stand-in stream is passed into the servlet using a response wrapper that overrides at least one of two methods—getOutputStream() and getWriter()—as demonstrated in Listing 14.11.

Listing 14.11 Simple Response Wrapper
package wlsunleashed.servlets;

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

public class SimpleResponseWrapper extends
HttpServletResponseWrapper
{
    private StringWriter output;


    public SimpleResponseWrapper(HttpServletResponse response)
    {
        super(response);
        output = new StringWriter();
    }

    public String toString() {
      return output.toString();
    }

    public PrintWriter getWriter()
    {
        return new PrintWriter(output);
    }
}

The general rule for overriding request and response methods is to extend servlet wrapper objects such as ServletRequestWrapper and HttpServletRequestWrapper for requests, and ServletResponseWrapper and HttpServletResponseWrapper for responses.

In the simple filter we've defined, we wrap the response in the SimpleResponseWrapper to enable modifying the processed response. The wrapped response is passed to the next entity in the filter chain; that is, the servlet instance in our case. The SimpleServlet writes the response to the stream that is embedded in the SimpleResponseWrapper.


PrintWriter out = response.getWriter();
SimpleResponseWrapper wrapper = new SimpleResponseWrapper(
   (HttpServletResponse)response);
chain.doFilter(request, wrapper);

When the chain.doFilter method returns, the servlet response can be retrieved from the wrapped output stream and customized to our needs. For example, we can add the date time value when the request was completed.



StringWriter strWr = new StringWriter();
strWr.write(wrapper.toString().substring(0, wrapper.toString().indexOf("</body>")-1));
      strWr.write("<p>\n<center> <font color='blue'>" + "Request Processed Time=" +new
graphics/ccc.gif java.util.Date() + "</font><center>");
strWr.write("\n</body></html>");
response.setContentLength(strWr.toString().length());
out.write(strWr.toString());
out.close();

The earlier code snippet modifies the response by adding a date value to the output and sends the output to the client using the print writer obtained from the original response object that was passed to the filter.

Configuring Filter Chains

Earlier, we briefly discussed a simple filter configuration. Let's look at the way that most Web containers (including WebLogic Server) implement filter chains. The container builds a filter chain using the order of filter mappings in the deployment descriptor, which can be determined by the URL pattern or the servlet name. In the simple filter configuration, we used the URL pattern to assign filters to the servlet. As an alternative, filters can be assigned using fully qualified servlet names, as shown here:


<web-app>
...
  <filter>
    <filter-name>sniffFilter</filter-name>
    <filter-class>wlsunleashed.servlets.SniffFilter</filter-class>
  </filter>
<filter>
    <filter-name>sniffFilter2</filter-name>
    <filter-class>wlsunleashed.servlets.SniffFilter2</filter-class>
  </filter>
  <filter-mapping>
    <filter-name> sniffFilter</filter-name>
    <servlet-name>/SimpleServlet/*</servlet-name>
  </filter-mapping>
  <filter-mapping>
    <filter-name> sniffFilter2 </filter-name>
    <servlet-name>/SimpleServlet/*</servlet-name>
  </filter-mapping>

...
</web-app>

In this configuration, sniffFilter and sniffFilter2 form the filter chain and are invoked in that order.

WebLogic Server also provides the capability to cache filter chains to improve performance because they don't have to be deciphered for every request.

Servlet Filter Class Reloading

WebLogic Server extends a servlet reloading feature, which has existed for quite some time, to automatically reload associated servlet filters. WebLogic Server checks the timestamp of the filter class prior to applying the filters and compares it to that of the instance in memory. If the version in memory is older than the servlet filter class, WebLogic Server reloads the filters before applying them. This feature comes in handy in development when there are frequent changes to the code. The developer can configure the interval at which the WebLogic Server automatically checks for reloading by using the Server Reload attribute, which is present on the descriptor tab for each Web application configuration, as shown in Figure 14.4.

    [ Team LiB ] Previous Section Next Section