The Servlet specification requires that every servlet container allow developers to log events to the container log file. Although the name and location of the log file are container-dependent, the manner in which these events are logged is dictated by the specification and is portable across web containers.
The javax.servlet.ServletContext class contains two methods that can be used for logging messages to the container's log:
public void log( String msg );
public void log( String msg, Throwable throwable );
You can use either of these methods by obtaining the ServletContext and passing the appropriate arguments. Example 15-1 illustrates how this can be done using a Struts Action.
public class LoginAction extends StorefrontBaseAction {
public ActionForward execute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response )
throws Exception{
// Get the user's login name and password. They should have already been
// validated by the ActionForm.
String email = ((LoginForm)form).getEmail( );
String password = ((LoginForm)form).getPassword( );
// Obtain the ServletContext
ServletContext context = getServlet().getServletContext( );
// Log which user is trying to enter the site
context.log( "Login email: " + email );
// Log in through the security service
IStorefrontService serviceImpl = getStorefrontService( );
UserView userView = serviceImpl.authenticate(email, password);
// Log the UserView for auditing purposes
context.log( userView.toString( ) );
UserContainer existingContainer = null;
HttpSession session = request.getSession(false);
if ( session != null ){
existingContainer = getUserContainer(request);
session.invalidate( );
}else{
existingContainer = new UserContainer( );
}
// Create a new session for the user
session = request.getSession(true);
existingContainer.setUserView(userView);
session.setAttribute(IConstants.USER_CONTAINER_KEY, existingContainer);
return mapping.findForward(IConstants.SUCCESS_KEY);
}
}
The LoginAction in Example 15-1 shows a very simple example of sending log messages to the container's log file. It calls the log( ) method and passes a literal string message that will be written to the log. As was mentioned earlier, the name and location of the log are dependent on which web container is being used.
Some web containers may assign a separate log file for each web application, while others may use just a single log file. If only one log file is used, messages from different web applications will end up in the same log file and generally will be prefixed with the web application name.
Filters are a new feature of the 2.3 Java Servlet specification. Servlet filters allow you to inspect and/or transform the content of the HTTP request and response objects. Because of the manner in which filters are invoked by the servlet container, they can operate on dynamic as well as static content.
|
Using filters, servlet developers can perform the following tasks:
· Access a web resource before a request to it is invoked.
· Process a request for a resource before it is invoked.
· Modify the request headers and data by wrapping the request with a customized version of the request object.
· Modify the response headers and data by wrapping the response with a customized version of the response object.
· Intercept a method call on a resource after it has been performed.
· Perform actions on a servlet, or group of servlets, by one or more filters in a specified order.
Based on the tasks required by servlet developers, the Servlet specification describes several possible types of filters:
· Authentication filters
· Logging and auditing filters
· Image conversion filters
· Data compression filters
· Encryption filters
· Tokenizing filters
· Filters that trigger resource-access events
· XSLT filters that transform XML content
· MIME-type chain filters
· Filters that cache URLs and other information
All of these types of filters are interesting, but in this chapter we are interested in using filters for logging and auditing. With filters, it's possible to log messages using any data that is contained in the request and response objects. Because the filter is coupled tightly to the servlet container, you'll probably still need logging functionality elsewhere in your application. Using filters for logging is generally not sufficient for the entire application. However, filters are perfect for auditing or tracking a user's actions through the system.
If you need to get an idea of which parts of your site your users are frequenting or where certain user groups are going most often, filters may be an ideal solution. They might even be able to give you information about the specific data that the users are viewing most often. For example, say that you have an online catalog application and you are interested in knowing which catalogs the users are browsing most often. Because the request and response objects contain this information, you can easily track this information and store it into a database for further analysis.
There are three basic steps for creating a filter:
1. Create a Java class that implements the javax.servlet.Filter interface and that contains a no-argument constructor.
2. Declare the filter in the web application deployment descriptor using the filter element.
3. Package the filter class along with the rest of the web application resources.
The first step in creating a servlet filter is to create a new Java class (or use an existing one) and have it implement the javax.servlet.Filter interface. Java classes can implement multiple interfaces, so you don't necessarily have to create a new class. However, the class will eventually need to be loaded by the web container, so it shouldn't be one that is installed only on a backend system, like an EJB container.
The Filter interface has three methods that must be implemented by your class:
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
public void destroy( );
The web container calls the init( ) method when it's ready to put the filter into service. You can initialize any needed resources in this method. The destroy( ) method is the opposite of the init( ) method. The web container calls this method when it takes the filter out of service. At this point, you should clean up any open resources that the filter may be using, such as database connections.
Finally, the web container calls the doFilter( ) method every time a request is received and the container determines that the filter instance should be notified. This is where you should place whatever functionality the filter is designed to perform.
Example 15-2 shows an example filter class that could be used to log to the servlet log file or to initialize a third-party logging service.
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletContext;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
/**
* An example servlet filter class.
*/
public class LoggingFilter implements Filter{
public final static String LOG_FILE_PARAM = "log_file_name";
private FilterConfig filterConfig = null;
private ServletContext servletContext = null;
public void init( FilterConfig config ) throws ServletException {
// Initialize any neccessary resources here
this.filterConfig = config;
this.servletContext = config.getServletContext( );
// You can get access to initialization parameters from web.xml
// although this example doesn't really use it
String logFileName = config.getInitParameter( LOG_FILE_PARAM );
// You can log messages to the servlet log like this
log( "Logging to file " + logFileName );
// Maybe initialize a third-party logging framework like log4j
}
public void doFilter( ServletRequest request,
ServletResponse response,
FilterChain filterChain )
throws IOException, ServletException {
// Log a message here using the request data
log( "doFilter called on LoggingFilter" );
// All request and response headers are available to the filter
log( "Request received from " + request.getRemoteHost( ) );
// Call the next filter in the chain
filterChain.doFilter( request, response );
}
public void destroy( ){
// Remove any resources to the logging framework here
log( "LoggingFilter destroyed" );
}
protected void log( String message ) {
getServletContext( ).log("LoggingFilter: " + message );
}
protected ServletContext getServletContext( ){
return this.servletContext;
}
}
|
The second step in creating a servlet filter is to configure the proper elements in the deployment descriptor for the web application. As you learned in Chapter 4, the name of the deployment descriptor file for a web application is web.xml.
The first step in setting up the filter declaration in the web application's deployment descriptor is to create the actual filter elements. Chapter 4 describes the filter element in detail. The following deployment descriptor fragment illustrates how it looks using the LoggingFilter class from the previous section:
<filter>
<filter-name>MyLoggingFilter</filter-name>
<filter-class>LoggingFilter</filter-class>
<init-param>
<param-name>log_file_name</param-name>
<param-value>log.out</param-value>
</init-param>
</filter>
You can also optionally specify initialization parameters, icons, a description, and a display label. See Chapter 4 for more details on the attributes of the filter element.
Once the filter element is added, you need to add a filter-mapping element that will associate or link the specified filter to a servlet or static resource in the web application. Filters can be applied to a single servlet or to groups of servlets and static content, using two distinct mapping approaches. The following deployment descriptor fragment illustrates a filter mapping to a single servlet called MyExampleServlet:
<filter-mapping>
<filter-name>MyLoggingFilter</filter-name>
<servlet-name>MyExampleServlet</servlet-name>
</filter-mapping>
Every time the web container receives a request for the MyExampleServlet resource, the doFilter( ) method in the LoggingFilter class will be invoked. The following XML fragment illustrates how the example filter can be mapped to all requests sent to the web application:
<filter-mapping>
<filter-name>MyLoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The filter mapping in this case will map all requests to the MyLoggingFilter because every request URI will match the "/*" URL pattern.
The final step is to package the filter class with the rest of the resources of the web application. As with any other Java resource that is part of a web application, the filter class must be bundled with the WAR file and able to be loaded by the web application's class loader. In most cases, the filter class should be placed under the WEB-INF/classes directory for the web application. Filter classes may also be inserted into a JAR file and placed in the WEB-INF/lib directory.
Web application event listeners are Java classes that implement one or more of the servlet event-listener interfaces. Event listeners support event notifications for changes in state in the ServletContext and HttpSession objects. Event listeners that are bound to the ServletContext support changes at the application level, while those that are bound to the HttpSession objects are notified for state changes at the session level.
Multiple listeners can be set up for each event type, and the servlet developer may offer a preference regarding the notification order for the listeners based on event type.
Tables Table 15-1 and Table 15-2 list the event types and event-listener interfaces available to the servlet developer.
Table 15-2. HttpSession application events and listener interfaces |
||
Event type |
Description |
Listener interface |
Lifecycle |
An HttpSession object has been created, invalidated, or timed out. |
HttpSessionListener |
Attributes |
Attributes on an HttpSession object have been added, removed, or replaced. |
HttpSessionAttributesListener |
The steps for creating an event listener are similar to those of creating filters. There are three primary steps to perform:
1. Create a Java class that implements the event-listener interface for which you are interested in receiving events. This class must contain a no-argument constructor.
2. Declare the event listener in the web application deployment descriptor using the listener element.
3. Package the event listener class along with the rest of the web application resources.
As when you create filters, the first step is to create a Java class that implements the appropriate listener interface. As an example, we'll create a Java class that implements the javax.servlet.ServletContextListener interface and is responsible for initializing the logging service when the web application is started. This class is illustrated in Example 15-3.
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* An
example event listener class that
* initializes a logging service.
*/
public class
LoggingListener implements ServletContextListener{
private ServletContext context = null;
/**
* Called by the container before the first request is
* processed. This is a good time to initialize
* the logging service.
*/
public void contextInitialized( ServletContextEvent event ){
this.context = event.getServletContext( );
// Initialize the logging service here
// Log a message that the listener has started
log( "LoggingListener initialized" );
}
/**
* Called by the container when the ServletContext is about
* ready to be removed. This is a good time to clean up
* any open resources.
*/
public void contextDestroyed( ServletContextEvent event ){
// Clean up the logging service here
// Log a message that the LoggingListener has been stopped
log( "LoggingListener destroyed" );
}
/**
* Log a message to the servlet context application log or
* system out if the ServletContext is unavailable.
*/
protected void log( String message ) {
if (context != null){
context.log("LoggingListener: " + message );
}else{
System.out.println("LoggingListener: " + message);
}
}
}
The event-listener class in Example 15-3 contains two methods that are invoked by the web container:
public void contextInitialized( ServletContextEvent event );
public void contextDestroyed( ServletContextEvent event );
The web container calls the contextInitialized( ) method before the first request is processed. You should initialize any needed resources in this method. For example, this is an ideal location to initialize a logging service. The contextDestroyed( ) method is called when the web application is taken out of service. This is where any open resources that the listener class is using should be closed.
Because there can be multiple event-listener classes for the same event, let's use another ServletContext event-listener class to make our example more realistic. Example 15-4 shows the DBConnectionPoolListener class. Both the LoggingListener and the DBConnectionPoolListener will receive event notifications when the ServletContext is initialized and destroyed.
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
/**
* An example event listener class that
* initializes the database connection pooling.
*/
public class DBConnectionPoolListener implements ServletContextListener{
/**
* Called by the container before the first request is
* processed. This is a good time to initialize
* the connection pooling service.
*/
public void contextInitialized( ServletContextEvent event ){
// Initialize the connection pooling here
}
/**
* Called by the container when the ServletContext is about
* ready to be removed. This is a good time to clean up
* any open resources.
*/
public void contextDestroyed( ServletContextEvent event ){
// Shut down the connection pooling and open database connections
}
}
Because both the LoggingListener and DBConnectionPoolListener are listening for the same type of application events, the servlet container will notify them in the order in which they are listed in the descriptor, based on the event type.
The following web application deployment descriptor fragment shows you how to set up the event listeners:
<web-app>
<listener>
<listener-class>LoggingListener</listener-class>
</listener>
<listener>
<listener-class>DBConnectionPoolListener</listener-class>
</listener>
<servlet>
<servlet-name>ExampleServlet</servlet-name>
<servlet-class>ExampleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ExampleServlet</servlet-name>
<url-pattern>/example</url-pattern>
</servlet-mapping>
</web-app>
The LoggingListener will be notified first, followed by the DBConnectionPoolListener instance. When the web application shuts down, the listeners are notified in reverse order. The HttpSession event listeners are notified prior to listeners for the application context.
The packaging of the event-listener classes follows the guidelines described in the previous section for filters. That is, the classes must either be in the WEB-INF/classes directory or be installed in a JAR file located in the WEB-INF/lib directory for the web application.