/*
* 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) {}
}
}
}
|