20.4 Servlet Initialization and Persistence: A Counter Servlet

Example 20-3 is a listing of Counter.java, a servlet that maintains any number of named counters. Each time the value of one of these counters is requested, the servlet increments the counter and outputs its new value. The servlet is suitable for use as a simple hit counter for multiple web pages but can also count any other type of event.

This servlet defines init( ) and destroy( ) methods and saves its state to a file, so it does not lose count when the web server (or servlet container) shuts down. To understand init( ) and destroy( ), you have to understand something about the servlet life cycle. Servlet instances are not usually created anew for each client request. Instead, once a servlet is created, it can serve many requests before it is destroyed. A servlet such as Counter is typically not shut down unless the servlet container itself is shutting down, or the servlet is inactive and the container is trying to free up memory to make room for other servlets.

The init( ) method is invoked when the servlet container first instantiates the servlet, before any requests are serviced. The first thing this method does is look up the value of two initialization parameters: the filename of the file that contains the saved state and an integer value that specifies how often to save the state back into that file. Once the init( ) method has read these parameters, it reads the counts (using object serialization) from the specified file and is ready to begin serving requests. The values of the initialization parameters come from the WEB-INF/web.xml deployment file. Before running this example, you probably need to edit that file to tell the servlet where to save its state. Look for these lines:

<init-param>   <param-name>countfile</param-name>         <!-- where to save state -->   <param-value>/tmp/counts.ser</param-value> <!-- adjust for your system--> </init-param>

If the filename /tmp/counts.ser does not make sense on your system, replace it with a filename that does.

The destroy( ) method is the companion to init( ); it is invoked after the servlet has been taken out of service and there are no more requests being processed by the servlet. The Counter servlet uses this method to save its state, so it can be correctly restored when the servlet container starts the servlet up again. Note, however, that the destroy( ) method is invoked only when the servlet is shut down in a controlled way. In the case of a server crash or power outage, for example, there is no opportunity to save state. Thus, the Counter servlet also periodically saves it state from the doGet( ) method, so it never loses more than a small amount of data.

The doGet( ) method must first determine the name of the counter whose value is to be displayed. Since the Counter servlet is designed for use in a variety of ways, doGet( ) uses three techniques for obtaining the counter name. First, it checks for a parameter sent with the HTTP request. Next, it checks for a request attribute, which is a named value associated with the request by the servlet container or by another servlet that has invoked the Counter servlet. (You'll see an example of this later in the chapter.) Finally, if the servlet cannot find a counter name using either of these techniques, it uses the URL through which it was invoked as the counter name. As you'll see shortly, the servlet container can be configured to invoke the servlet in response to any URL that ends with the suffix .count.

Note that the doGet( ) method contains a synchronized block of code. The servlet API allows multiple threads to execute the body of doGet( ) at the same time. Even though many threads representing many different user sessions may be running, they all share a single data structure the hashtable of named counts. The synchronized block prevents the threads from accessing (and possibly corrupting) the shared data structure at the same time. Finally, note the use of the log( ) method. When asked to start counting with a counter name it has never used before, the servlet uses this method to produce a permanent record of the event in a log file.

Example 20-3. Counter.java
package je3.servlet; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; /**  * This servlet maintains an arbitrary set of counter variables and increments  * and displays the value of one named counter each time it is invoked.  It  * saves the state of the counters to a disk file, so the counts are not lost  * when the server shuts down.  It is suitable for counting page hits, or any  * other type of event.  It is not typically invoked directly, but is included  * within other pages, using JSP, SSI, or a RequestDispatcher  **/ public class Counter extends HttpServlet {     HashMap counts;      // A hash table: maps counter names to counts     File countfile;      // The file that counts are saved in     long saveInterval;   // How often (in ms) to save our state while running?     long lastSaveTime;   // When did we last save our state?     // This method is called when the web server first instantiates this     // servlet.  It reads initialization parameters (which are configured     // at deployment time in the web.xml file), and loads the initial state     // of the counter variables from a file.     public void init( ) throws ServletException {         ServletConfig config = getServletConfig( );         try {             // Get the save file.             countfile = new File(config.getInitParameter("countfile"));             // How often should we save our state while running?             saveInterval =                  Integer.parseInt(config.getInitParameter("saveInterval"));             // The state couldn't have changed before now.             lastSaveTime = System.currentTimeMillis( );             // Now read in the count data             loadState( );         }         catch(Exception e) {             // If something goes wrong, wrap the exception and rethrow it             throw new ServletException("Can't init Counter servlet: " +                                        e.getMessage( ), e);         }     }     // This method is called when the web server stops the servlet (which     // happens when the web server is shutting down, or when the servlet is     // not in active use).  This method saves the counts to a file so they     // can be restored when the servlet is restarted.     public void destroy( ) {         try { saveState( ); }  // Try to save the state         catch(Exception e) {  } // Ignore any problems: we did the best we could     }     // These constants define the request parameter and attribute names that     // the servlet uses to find the name of the counter to increment.     public static final String PARAMETER_NAME = "counter";     public static final String ATTRIBUTE_NAME =         "je3.servlet.Counter.counter";     /**      * This method is called when the servlet is invoked.  It looks for a      * request parameter named "counter", and uses its value as the name of      * the counter variable to increment.  If it doesn't find the request      * parameter, then it uses the URL of the request as the name of the      * counter.  This is useful when the servlet is mapped to a URL suffix.      * This method also checks how much time has elapsed since it last saved      * its state, and saves the state again if necessary.  This prevents it      * from losing too much data if the server crashes or shuts down without      * calling the destroy( ) method.      **/     public void doGet(HttpServletRequest request, HttpServletResponse response)         throws IOException     {         // Get the name of the counter as a request parameter         String counterName = request.getParameter(PARAMETER_NAME);                  // If we didn't find it there, see if it was passed to us as a         // request attribute, which happens when the output of this servlet         // is included by another servlet         if (counterName == null)             counterName = (String) request.getAttribute(ATTRIBUTE_NAME);         // If it wasn't a parameter or attribute, use the request URL.         if (counterName == null) counterName = request.getRequestURI( );         Integer count;  // What is the current count?         // This block of code is synchronized because multiple requests may         // be running at the same time in different threads.  Synchronization         // prevents them from updating the counts hashtable at the same time         synchronized(counts) {             // Get the counter value from the hashtable             count = (Integer)counts.get(counterName);                          // Increment the counter, or if it is new, log and start it at 1             if (count != null) count = new Integer(count.intValue( ) + 1);             else {                 // If this is a counter we haven't used before, send a message                 // to the log file, just so we can track what we're counting                 log("Starting new counter: " + counterName);                 // Start counting at 1!                 count = new Integer(1);             }                          // Store the incremented (or new) counter value into the hashtable             counts.put(counterName, count);             // Check whether saveInterval milliseconds have elapsed since we             // last saved our state.  If so, save it again.  This prevents             // us from losing more than saveInterval ms of data, even if the             // server crashes unexpectedly.               if (System.currentTimeMillis( ) - lastSaveTime > saveInterval) {                 saveState( );                 lastSaveTime = System.currentTimeMillis( );             }         }  // End of synchronized block                  // Finally, output the counter value.  Since this servlet is usually         // included within the output of other servlets, we don't bother         // setting the content type.         PrintWriter out = response.getWriter( );         out.print(count);     }     // The doPost method just calls doGet, so that this servlet can be     // included in pages that are loaded with POST requests     public void doPost(HttpServletRequest request,HttpServletResponse response)         throws IOException     {         doGet(request, response);     }     // Save the state of the counters by serializing the hashtable to     // the file specified by the initialization parameter.     void saveState( ) throws IOException {         ObjectOutputStream out = new ObjectOutputStream(                     new BufferedOutputStream(new FileOutputStream(countfile)));         out.writeObject(counts);  // Save the hashtable to the stream         out.close( );              // Always remember to close your files!     }     // Load the initial state of the counters by de-serializing a hashtable     // from the file specified by the initialization parameter.  If the file     // doesn't exist yet, then start with an empty hashtable.     void loadState( ) throws IOException {         if (!countfile.exists( )) {             counts = new HashMap( );              return;         }         ObjectInputStream in = null;         try {             in = new ObjectInputStream(                    new BufferedInputStream(new FileInputStream(countfile)));             counts = (HashMap) in.readObject( );         }         catch(ClassNotFoundException e) {             throw new IOException("Count file contains bad data: " +                                   e.getMessage( ));         }         finally {             try { in.close( ); }             catch (Exception e) {  }         }     } }

20.4.1 Running the Counter Servlet

Our web.xml file assigns the name counter to the Counter servlet. The servlet reads a request parameter, also named counter, to find the name of the counter variable to increment and display. Thus, you can test the servlet by pointing your browser to a URL like the following:

http://localhost:8080/je3/counter?counter=numhits

The web.xml file also specifies that the Counter servlet should be invoked whenever a file ending with the suffix .count (and beginning with the web application name) is requested from the web server. (You'll learn how the web.xml file specifies this later in this chapter.) In this case, Counter uses the URL itself as the name of the counter variable. Test this feature by entering a URL like the following:

http://localhost:8080/je3/index.html.count

By now, you've probably noticed that the Counter servlet doesn't produce very interesting output: it just displays a number. This servlet is not very useful when used on its own; it is intended, instead, to have its output included within the output of other servlets. (We'll explore a couple of ways to do this later in the chapter.) If your web server supports server-side includes (SSI) and the <servlet> tag, though, you can use this feature to include the output of the Counter servlet in web pages. (Tomcat 5 can be configured to support SSI, but does not do so by default.) To do so, you might create a test.shtml file that contains text like the following:

<p>This page has been accessed    <servlet name='/counter'>     <param name='counter' value='test.shtml'></param>   </servlet> times.


Java Examples in a Nutshell
Java Examples in a Nutshell, 3rd Edition
ISBN: 0596006209
EAN: 2147483647
Year: 2003
Pages: 285

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net