FileDocCategorySizeDatePackage
Counter.javaAPI DocExample7317Sat Jan 24 10:44:40 GMT 2004je3.servlet

Counter.java

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.com/javaexamples3.
 */
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) {}
	}
    }
}