HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

With the release of the Java 2 Platform, Enterprise Edition, Java-based Web application servers are gaining in popularity. Although application servers have been around for a few years, they forced programmers to be tied to a proprietary API. Support of J2EE by application server vendors standardizes the API we write to, easing training, staffing and support costs. Perhaps most important in our dynamic Web vendor environment, writing code to an industry standard reduces the huge risk a customer takes in choosing a particular vendor should that vendor disappear.

In this article I'll explain the basics of Java servlet sessions and why careful monitoring of your sessions will be important in a large site using multiple application servers. Then I'll present a simple utility to check your servlet sessions.

Application Servers
What is a Web "application server"? Although many full-fledged Web applications have been written using a Web server with cgi-bins, most are written using C or Perl. The cgi-bins typically contained display and business logic and talked to back-end processors and databases via proprietary interfaces (see Figure 1). This works fine for simple applications with low traffic, but when traffic increases or pages need to be redesigned, the situation gets ugly.

Figure 1
Figure 1:

Unless it was designed with saintlike attention to isolation and modularity, a page change, database change or business logic change ripples through all parts of the application. More important, as traffic goes up, more and more code must be dedicated to resource management: database connection management to keep the database from getting swamped, thread or process control to keep the Web server from thrashing, and load distribution to handle volume. Sound scary? Ask anyone who's had to maintain an older site for a few years using only a C compiler.

Application servers help reduce the housekeeping and management burdens, allowing developers to concentrate on the application itself. On the presentation layer a page layout mechanism enables the use of templates to insert data in dynamic pages. A transaction monitor allows you to manage the sequence of transactions, roll back across heterogeneous databases, use a connection pool and access a common API to address different databases. Business logic modularity is supported through formal mechanisms. Automatic caching is available for database resultsets and HTML pages. Across it all are management functions to perform load balancing and distribution among threads, to make full use of available processors and to allow for graceful degradation under stress. The most serious application servers also support load balancing or failover between servers.

Java-based application servers perform these functions using a standard set of APIs (see Figure 2). Instead of learning the quirks of your vendor's API, you can rely on information from multiple sources and have access to many resources - books, sites and newsgroups - not to mention the fine magazine you're reading now. You also avoid the sometimes deadly vendor lock. Using a proprietary system is like locking yourself in a jail cell; a standards-based system is similar, but you get to smuggle in a hacksaw.

Although four layers are shown in the application server in Figure 2, there's nothing stopping you from skipping layers or using a more complex pathway. For instance, you may find that for all their sophistication EJBs are overkill. You may choose to go directly from a servlet to a JDBC call. However, to get business-layer isolation you'll probably want to use a bean separate from servlet code. Also, it's not uncommon to have a chain of servlets to provide layered user data validation and transaction control.

Figure 2
Figure 2:

Presentation Layer
At the presentation layer, user data validation and HTML page construction are accomplished with Java servlets and JavaServer Pages. Servlets and JSPs, or a chain of them, can be invoked. A servlet is a subclass of the HttpServlet interface. The application server invokes a method of your servlet through a mapping of URL nodes (see Figure 3 for an example). An HttpRequest object is passed to the invoke() method of your HttpServlet; HttpRequest contains everything that gets passed into a cgi-bin and more, but with easier access. The request is examined and processed, and your servlet returns a stream of HTML through another parameter object.

Figure 3
Figure 3:

JSPs allow you to mix servlet code with HTML, which gives you a nice layout of static HTML with the dynamic bits placed as Java code. JSP source is compiled into an HTML file and a Java file, but the application server does it for you. Some servers even recompile on the fly, recognizing a change to JSP source and doing all the compiling and class reloading automatically (pretty slick). JSPs can do most things a servlet can, and in addition support JavaBeans.

You probably know about beans, but for the purpose of JSPs, the only important rule to follow is the coding pattern of using getProperty() and setProperty() methods - where "Property" is a bean property name that's usually just a field of the bean class. Since arbitrary Java code can be executed in a JSP, a bean can be used just by invoking it as a class in the usual way. Beyond this, though, there's support for beans. You can create and use a bean for a period that extends beyond the life of the current request (see Table 1). There's also a JSP syntax to set and get bean properties, although standard Java syntax works just as well.

Table 1

Typically, the beans invoked from a JSP contain your business logic, and directly or indirectly invoke back-end systems and databases. Even if your logic is simple, you're better off minimizing the amount of Java in a JSP. Think of a JSP's Java code as performing just enough logic to invoke business beans. The beans do their work, then the JSP extracts bean properties to put data on the HTML page.

Sessions
An HttpSession object is part of the HttpRequest object passed to a servlet or JSP; HttpSession is the easy way to have data associated with a user (actually the user's browser). Behind the scenes, the application server uses cookies or URL encoding to indirectly store the data. This is probably nothing new to you; cookies or URL codes are the only way to store data on the browser so it gets returned to the server on subsequent HTTP requests. The friendliness of HttpSession is twofold. First, you don't have to think about cookies or URLs since the server does that dirty work for you. In fact, some servers will try to use cookies and if they can't (if they're not accepted by the browser, for instance), the server will attempt to use URL rewriting instead. Second, the application data is stored on the server, not the browser. The cookie data is merely a key to finding the session data. This gives you a good measure of security for your data as well as freeing you (mostly) from the space constraints of storing data directly in a cookie.

You can store an object in a session by invoking a method of HttpSession and giving the object a name. On the same or subsequent requests, invoking a get method with the object's name can retrieve the object. A servlet accesses the HttpSession directly. A JSP can also use the session directly but, as you probably guessed, a bean stored with a "session" lifetime is stored into and retrieved from the HttpSession for you.

Distributing Servlet Sessions
If you buy a big, serious Java application server, one of the features your money buys is load balancing and failover mechanisms. Within a server box, your app server package is expected to partition work among available resources as efficiently as possible. Among other things, that means clever distribution of work between available CPUs and pooling connections to back-end systems.

Distributing fine-grained work between servers is even more clever. However, if your application is supposed to work with a consistent HttpSession, the session must be maintained on multiple servers (see Figure 4). Application servers can have more than one policy for sharing sessions, but it's clear that if you need to have robust transactions (that is, user transactions that can survive if a server goes down) your HttpSessions must somehow be distributed to the peer server(s). Although it's technically possible for a server to implement distributed sessions without using serialization, the serialization mechanism native to Java is the obvious and simplest way for a server to distribute the data from the objects living in the session. Even though the app server does the hard work of enforcing session distribution policies, you have to make sure all the objects you store into HttpSession are serializable.

Figure 4
Figure 4:

Serializing Session Objects
If you're not familiar with Java serialization, here's a brief overview. For more information, see the Javadoc for the serializable interface in the standard JDK.

Serialization is at the core of several Java standards involving the movement or storage of the object state outside of system memory. It's a powerful mechanism but simple to use. An object that implements the java.io.Serializable interface can have its state, and the state of all constituent objects, written to a java.io.ObjectOutputStream. The object can then be reconstituted by reading the same stream from an ObjectInputStream.

To allow an object to be serialized, a programmer need only specify "implements Serializable" and be sure all constituent objects are serializable (or primitive) as well. Notably, a programmer doesn't need to code any methods at all to support serialization. Just about any JDK class you'd sensibly want to serialize already implements serialization.

Although it's easy to mark an object as serializable, the subtle danger is that it's easy to forget to mark an object deep in a hierarchy. You'd learn about the oversight only at runtime when ObjectOutputStream throws a NotSerializableException, and then only if you're vigilant. The size of a serial stream can be difficult to predict for the same reason: it's often not surprising to find that a field of your object was set to an object pointing to a large tree of objects. During serialization, you may find that the tree-following serialization process dutifully saves a lot of data you don't actually need.

Session Monitor
I've written a simple HttpSession display utility that could be useful to you in different ways, depending on where you are on the path toward J2EE.

  1. In a development environment, to monitor the size and structure of objects stored in sessions, possibly by different development teams. This was the original purpose of the utility.
  2. To help study the composition of sessions while you're experimenting and learning. To that end, if you don't want to buy a commercial app server yet (for the price of a car), you can learn with the reference version of J2EE generously made available from Sun at http://java.sun.com/j2ee.
  3. To give you a simple example of a JSP working with a JavaBean. Also, it demonstrates the power of manipulating classes as objects.

The JSP "SessionTester.jsp" is in Listing 1 and the worker bean "SessionTesterBean.java" is in Listing 2. In the JSP you can see how easily Java and HTML are mixed. Note that "<%" ... "%>" surrounds Java statements, while "<%=" ... "%>" surrounds a Java expression that's converted to a string and inserted in the surrounding HTML. (I have to comment how wonderful it was to start using JSP after building cgi-bins for years in C. It was like emerging from a programmer's gulag.)

It's a stretch to call the SessionTesterBean class a bean. But the term bean is an accurate way to differentiate an independent Java class in an app server from a servlet or JSP. Plus, it sounds better than "Java worker class." The SessionTesterBean consists of a few static methods, one of which is called from the JSP. Each value in the session is found, then dumped by writing HTML to the "out" stream. Note the direct way I find the serialized size of each session value object - I just write the object to an ObjectOutputStream and find the number of bytes. I also check for exceptions and report them, which helps find any nonserializable object in your object tree.

Installing the utility can be different for every server vendor. Compile the .java file with the compiler in your app server's JDK. If necessary, "compile" the .jsp file, although many app servers automatically recognize, compile and install new JSPs. You have to put the .jsp file in a location that allows the app server to invoke it with a URL, and you have to put the .class file in a package directory, SessionTesterBeans, along the CLASSPATH that's active when the JSP is active. This may involve configuration efforts in your app server, but if you've already done some playing with JSPs and beans, you probably already know where to install the files in an existing configuration.

To use the utility, establish some HttpSession data in your own servlet or JSP, then simply use the URL needed to invoke SessionTester.jsp. The page returned shows a summary of the objects in the current session along with their sizes. Pressing a button displays session parameters and the composition of each object within it.

Author Bio
Peter Kobak is a technical lead for a major financial institution's Web site. He's currently part of a team working to migrate the site from a proprietary application infrastructure to a Java-based application server.
He can be reached at: [email protected]

	

Listing 1: 

<HTML> 
<TITLE>Session Unit Tester</TITLE> 
<H1>Session Unit Tester</H1> 

<FONT COLOR="Teal">Although caching is 
 turned off on this page, it is safer 
 to do a manual refresh to be sure the 
 most current 
 session is examined.</FONT> 

<% 
   //Turn off all browser caching 
   response.setHeader("pragma","no-  cache"); 
   response.setHeader("cache control","no"); 
   response.setHeader("expires","0"); 
  
   HttpSession session = 
      request.getSession(false); 
   if (session != null) 
   { 
      boolean fDeep = 
         request.getParameter("De- 
         tails") != null; 
      String buttonName = fDeep ? 
           "VALUE=\"Hide Details\"" : 
           "NAME=\"Details\" "+ 
              "VALUE=\"Show Details\""; 
%> 
<FORM METHOD="GET"> 
   <INPUT TYPE="SUBMIT" <%= buttonName%> > 
</FORM> 
<% 
      SessionTesterBeans.SessionTesterBean. 
         dumpSession( session, out, fDeep ); 
   } 
   else 
   { 
%> 

<H3>Couldn't get a session :-(</H3> 

Suggestions for getting a session:<OL> 
<LI>Hit the refresh button. 
<LI>Make sure you have a session in 
 your application; it may have expired. 
 Try to start a new session in your 
 application. 
<LI>Check the application's session 
 configuration. 
</OL> 

<% 
   } // end else 
%> 

</HTML> 
  

Listing 2: 
  
package SessionTesterBeans; 

import java.beans.*; 
import java.util.*; 
import java.text.*; 
import java.io.*; 
import java.lang.reflect.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 
import java.beans.*; 
import java.util.Date; 

/** 
* A set of static methods to test and 
* dump servlet sessions. Normally to be 
* used from SessionTester.jsp, but can 
* also be used from the command line 
* (see main()) or by calling these 
* methods directly. 
* <P> 
* All dumping methods accept an object 
* and an output stream. 
* The display of the object is created 
* as HTML and written to the stream. 
*/ 
public class SessionTesterBean { 

// don't display property values longer 
// than this 
static final int MAX_DISPLAYABLE_STRING = 100; 

// time format used by epochTimeToString() 
static final SimpleDateFormat myDateFormat = 
   new SimpleDateFormat( "HH:mm:ss.SSS 
                       'on' "+ 
                       "MM/dd/yyyy" ); 

/** 
* Diplays the content of a session, 
* including indentification of 
* each object stored in the session. 
* If fDeep is true, each such object 
* is also displayed using dumpSerializ- 
* able() and dumpBean() in this class. 
* @param session Session to be dis- 
* played. 
* @param out The stream to which HTML 

* is written. 
* @param fDeep True to perform a dump 
* of each session object. 
*/ 
public static void 
dumpSession( HttpSession session, 
            PrintWriter out, 
            boolean fDeep ) 
{ 
   if (session == null) { 
      out.println( "<BR>Null session 
      passed to "+ 
         "SessionUnitTester.dumpSes- 
          sion()" ); 
      return; 
   } 

   if (fDeep) { 
      out.println( "<BR>Current  time: " + 
         myDateFormat.format(new Date())); 
      out.println( "<BR>Access   time: " + 
         myDateFormat.format(new Date( 
            session.getCreationTime())) ); 
      out.println( "<BR>Creation time: " + 
         myDateFormat.format(new Date( 
            session.getLastAccessed- 
            Time())) ); 
      out.println( "<BR>session id: " + 
         session.getId() ); 
      out.println( "<BR>timeout: " + 
         session.getMaxInactiveInterval() ); 
      out.println( "<BR>new: " + 
         session.isNew() ); 
   } 

   String[] names = session.getValue- 
   Names(); 
   out.println( "<P>Found "+ 
                names.length + 
                " session 
                objects:<OL>" ); 

   Object sessObj; 
   int    totalObjSize = 0; 

   for (int i = 0; i < names.length; ++i) { 
      out.println( "<P><LI>" + names[i] ); 
      sessObj = session.getValue( names[i] ); 
      if (sessObj == null) { 
         out.println( 
            "<BR><B>Can't get this ses- 
             sion "+ 
            "object.</B>" + 
            " Possibly non-Serializ- 
              able." ); 
      } 
      else { 
         totalObjSize += 
            dumpSerializable( sessObj, 
                             out, 
                             fDeep ); 

         /* Bean dumping code that's 
         /* too long to show in this 
         /* article 
            if (fDeep) { 
               dumpBean( sessObj, out ); 
            } 
         */ 
      } 
   } 
   out.println( 
      "</OL>Total size of session 
       objects = " + 
      totalObjSize + "<BR>" ); 
} 

/** 
* Displays a (supposedly) Serializable 
* object by attempting to write the 
* object to determine if the object is * serializable, and if so, how big 
* the object is. If fDeep is true, dis- 
* plays the name of each field in the 
* class. 
* <P>The purpose of this display is to 
* assist the developer in determining 
* if an object can be serialized in a 
* servlet session, and to report the 
* size and composition of the object 
* so it can be controlled at an early 
* stage in the development process. 
* @param sessObj An object to be dis- 
* played. 
*        Although Serializable objects 
*        are expected, any object can 
*        be used. 
*        Non-serializable objects 
*        (including non-serializable 
*        objects embedded in 
*        sessObj) will be reported as 
*        such. 
* @param out The stream to which HTML 
* is written. 
* @param fDeep True to display the 
* fields of the object. 
* @return Serialized size of sessObj 
* (zero if couldn't be serialized). 
*/ 
public static int 
dumpSerializable( Object sessObj, 
                 PrintWriter out, 
                 boolean fDeep ) 
{ 
   if (sessObj == null) { 
      out.println( "<BR>Null object 
      passed to "+ 
         "SessionUnitTester.dumpSerial- 
          izable()" ); 
      return 0; 
   } 

   Class sessClass = sessObj.get- 
   Class(); 
   out.println( "<BR>Class name = "+ 
                sessClass.getName() 
                +"<BR>" ); 

   int sessObjSize = 0; 

   try { 
      ByteArrayOutputStream baSessStrm = 
         new ByteArrayOutputStream(); 
      ObjectOutputStream objSessStrm = 
         new ObjectOutputStream( 
         baSessStrm ); 
      objSessStrm.writeObject( sessObj ); 
      objSessStrm.close(); 
      sessObjSize = baSessStrm.size(); 
      out.println( "Serialized size = "+ 
                   sessObjSize ); 
   } 
   catch (Exception xcpt) { 
      if (xcpt instanceof 
          NotSerializableException) { 
         out.println( "<B>This object 
                      is not "+ 
                      "serializ- 
                      able.</B><BR>" ); 
      } 
      out.println( "While attempting to "+ 
                  "serialize, this 
                  exception "+ 
                  "occurred:<BR>" ); 
      xcpt.printStackTrace( out ); 
   } 

   if (fDeep && !(sessClass.isInterface() || 
               sessClass.isPrimitive() || 
               sessClass.isArray()       ) ) { 
      Field[] fields = 
         sessClass.getDeclaredFields(); 
      if (fields.length > 0) { 
         out.println( "<P>found "+ 
                      fields.length + 
                      " object 
                      fields:<OL>" ); 
         for (int ifield = 0; 
              ifield < fields.length; 
              ++ifield) { 
            out.println( "<LI>" + 
                fields[ifield].to- 
                String() ); 
         } 
         out.println( "</OL>" ); 
      } 
      else { 
         out.println( 
            "<P>no object fields found" ); 
      } 
   } 

   return sessObjSize; 
} 

/** 
* Stand-alone test for an object. 
* The program argument is the name of a 
* class. 
* If the class can be loaded and an 
* object created, that object is dis- 
* played (with 
* HTML tags) using <B>dumpSerializ- 
* able</B>. 
*/ 
public static void main( String[] args ) 
{ 
   PrintWriter out = 
      new PrintWriter( System.out ); 

   out.println( "Argument is name of 
                class to "+ 
                "test for session 
                properties." ); 
   if (args.length == 1) { 
      final String testClassName = 
      args[0]; 
      try { 
         Class testClass = 
            Class.forName( testClass- 
            Name ); 
         Object testObject = 
            testClass.newInstance(); 
         dumpSerializable( testObject, out, 
                           true ); 
      } 
      catch (Exception xcpt) { 
         out.println( 
            "Couldn't create an 
            instance of "+ 
            testClassName ); 
         xcpt.printStackTrace( out ); 
      } 
   } 
   out.flush(); 
} 

} // end class SessionTesterBean 



 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.