ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Learning Servlet Filters

by Satya Komatineni
08/29/2001

The implementation of filters is a new mechanism being introduced into the Servlets 2.3 standard. This article explores the implications of filters to the Servlet architecture.

It has been a pet peeve of mine that one should not buy into an entire EJB solution just to gain the transparent (container-managed) transactional support for Java objects dealing with relational databases. One could inquire, what is the cost of implementing such a solution in the servlet tier itself? Because transactional support is valuable, whether the solution is distributed or not.

Typically, this transparent transactional support is accomplished by enrolling the active thread with a connection pool manager that the application relies upon for connections. The filtering mechanism is positioned to intercept the calls to an eventual servlet by registering the current thread with a connection pool manager in order to accomplish this. As one can see, the filtering mechanism is ideally suited for interposition, similar in concept to the EJB interposition of remote object calls from the EJB object to the bean instance.

Using the same interposition trick, one could also enroll a logging mechanism with the current thread as well and thereby provide applications with server-side logging that delineates at the thread level, as opposed to interlacing the log messages.

For those types of filters that do not interfere with the request or response, interposition could be adapted even in the current releases of servlets, and can provide an invaluable service to the developers that are currently bogged down by managing their own transactions programmatically. When this is combined with higher level data-access mechanisms (paralleling ADO from Microsoft or TDP(Transparent Data Pipeline) architecture that I have proposed in some previous articles), programmers can be completely oblivious to database connections altogether. This is the stage where developers can write business logic with the same ease that the database developers enjoy today with stored procedures; native and quick. I intend to demonstrate here the ease and simplicity of this process with some concrete code examples.

Developers' View of the Facilities

Related Reading

Java Servlet Programming, 2nd EditionJava Servlet Programming, 2nd Edition
By Jason Hunter with William Crawford
Table of Contents
Index
Sample Chapter
Full Description

The best way to gauge the usefulness of a facility (or framework) is by looking at some sample code that actually uses that facility. As suggested, filters and transactional support are implemented by the framework, and the developer code has no reference to those objects. The servlet programming model is a request/response model, so a developer typically interprets and executes a request by dividing his work into a series of tasks. These tasks could include database queries and updates, and perhaps multiple updates requiring transactional integrity.

The ITask interface is introduced here to represent the concept of a task. Given a task name and arguments, it will perform an abstract task and return an object and throw an exception to indicate exceptional failures. Many tasks can be derived from this task, and this gives us a mechanism to treat and manage all tasks as ITasks. Please keep in mind that these concepts are introduced here just to explain transactional support. In your own program, you are free to have your own class hierarchies and methodology and to never ever refer to ITask.

Interface ITask {
  Object executeTask( String taskName, Object Arguments ) throws TaskExecutionException;
}

Let us write a task that allows us to add an employee to a company:

class AddEmployeeTask implements ITask
{
  Object executeTask(String taskName, Object args) throws
TaskExecutionException
  {
    try
    {
      // Get a connection pool manager
      IConnectionPoolManager icpm = Factory.getInstance().getObject(IConnectionPoolManager.NAME);

      // From the connection pool manager
      // obtain a connection
      SQLConnection con = icpm.getConnection("data_source_1");

      .. use the connection to store the data in the
database
      .. return any required object
    }
    finally
    {
      // return the connection back to the pool
      icpm.putConnection(con);
    }
  }
}

Now let us write another task that would allow us to update an employee:

class UpdateEmployeeTask implements ITask
{
  Object executeTask(String taskName, Object args) throws
TaskExecutionException
  {
    try
    {
      IConnectionPoolManager icpm = Factory.getInstance().getObject(IConnectionPoolManager.NAME);
      SQLConnection con = icpm.getConnection("data_source_1");
      .. use the connection to store the data in the database
      .. return any required object
    }
    finally
    {
      icpm.putConnection(con);
    }
  }
}

Now assume, for some reason, we need to add a collection of employees:

class AddEmployeesTask implements ITask
{
  Object executeTask(String taskName, Object args) throws TaskExecutionException
  {
    // No need to get the connection manager here
    // We also assume the enumeration of employees is passed through the args
    Enumeration e = (Enumeration)args;

    AddEmployeeTask addEmployeeTask = new AddEmployeeTask();
    for(;e.hasMoreElements();)
    {
      addEmployeeTask.executeTask(AddEmployeeTask.NAME, e.nextElement());
    }

  }
}

If you have noticed, the individual AddEmployeeTask that independently acquires and returns the connection can now work well even, when it is used in a compositional context. This paradigm allows developers to organize their work as independent units and test them while allowing them for future compositions. So each task, whether composed or individual, will always get executed in a transactional context. That means the transactional filter we are talking about will automatically commit or roll back these tasks, depending on the exceptions that were raised. Although I prefer exceptions as the main channel for these automatic commit/rollbacks, one could imagine a different scheme (either via return codes or http response attributes) that could be implemented. Now it remains to be seen how the transactions are actually commited and rolled back.

The Magical Connection Pool Manager

Let us investigate where this magic is taking place, allowing us to return the same connection when executed on the same thread. Obviously, the trick lies within the implementation of the IConnectionPoolManager class. As a matter of good practice, the implementation of this class is obtained via a factory, allowing us to migrate to an EJB instance when it is justified, while keeping the rest of the code unchanged.

interface IConnectionPoolManager
{
  SQLConnection getConnection(String dataSourceName) throws SQLException;
  void putConnection(SQLConnection con) throws SQLException;
}

TransactionalConnectionPool implements IConnectionPoolManager
{
  // Hashtable<Thread,SQLConnection>
  Hashtable threadVsConnection = new Hashtable();

  String m_getConnectionLock = "lock";

  SQLConnection getConnection(String datasourceName) throws SQLException
  {
    SQLConnection con = (SQLConnection)threadVsConnection.get(datasourceName);
    if (con != null)
    {
      return con;
    }
    // no connection available
    synchronized(m_getConnectionLock)
    {
      SQLConnection con = (SQLConnection)threadVsConnection.get(datasourceName);
      if (con != null)
      {
        return con;
      }
      con = m_dataSources.get(dataSourceName).getConnection();
      threadVsConnection.put(getCurrentThread,con);
    }
  }

  void putConnection(SQLConnection con) throws SQLException
  {
    .. return connection to the underlying data source either by closing it or returning it
  }

  // called by the filter to remove any
  // connections associated with this thread
  void removeAssociation()
  {
    threadVsConnection.remove(getCurrentThread());
  }
  
  // returns the current thread
  Thread getCurrentThread()
  {
    ....
  }
  
  void commitForThisThread()
  {
    SQLConnection con = threadVsConnection.get(getCurrentThread());
    ..commit logic
  }
  void rollbackForThisThread()
  {
    SQLConnection con = threadVsConnection.get(getCurrentThread());
    .. rollbacklogic
  }
}

As you can see, when you ask the connection pool manager to give you a connection, it checks to see if the current thread is already associated with a connection. If it is, the connection pool manager will return the same connection. If not, it will contact the necessary data source, obtain a connection, and register with the current thread. Eventually, the filter will remove this threadVsConnection association when it is no longer required.

Now that we have seen how the connection pool manager coordinates threading with connections, it is time to see how the filters could use this property to effectively demarcate transactions.

Filter Code

The references included in this article are sufficient to understand filters. Unlike EJBs and like Servlets, the spec is small and can be mastered in a single sitting. To summarize the spec: the servlet container will call a series of Java objects that are registered for a given URL. These objects are called filters. Each filter will control the input and output of the downstream filter. The servlet itself is conceptually at the end of this chain. Besides the initialization and destroy methods, there is only one significant method that we need to worry about. And the code below overrides that doFilter method to intercept calls to the intended servlets requiring transactional support.

class TransactionFilter implements Filter
{
  // An implementation of the IConnectionPoolManager
  // and obtainedat the filter initialization
  private TransactionConnectionPoolManager icpm = null;
  ..
  void doFilter( ServletRequest request, ServletResponse response, FilterChain chain)
  {
    try
    {
      chain.doFilter(request,response,chain);
      icpm = Factory.getInstance().getObject(IConnectionPoolManager.Name);
      icpm.commitForThisThread();
    }
    catch(..)
    {
      .. analyze exception to see if this requires a rollback
      .. for commit
      icpm.rollbackForThisThread();
    }
    finally
    {
      // remove the connection from the connection pool
      if (icpm != null)
      {
        icpm.removeAssociation();
      }
    }
  }
}

The principle here is quite simple: at the end of the servlet request, just tell the connection pool manager that the transaction has ended (whether for good or bad) by calling its removeAssociation().

Observations

Assumptions

Thread-Based Logging

The same trick could be applied for thread-specific logging as well:

interface ILog
{
  void log(String message);
  void log(String message, Throwable);
}

class PerThreadLog implements ILog
{
  // Hold messages on a thread by thread messages
  Hashtable threadVsMesaageVector = new Hashtable();
  
  void log(String message)
  {
    Vector messageVector =
threadVsMessageVector.get(getCurrentThread());
    messageVector.add(..)
    ..
  }
  
  void initializeMessages()
  {
    threadVsMessageVector.put(getCurrentThread(),new Vector());
  }

  void dumpMessages()
  {
    Vector messageVector =
threadVsMessageVector.removet(getCurrentThread());
    ..lock(stream) // or you can transfer this job to a lower
priority serialized thread
    ..Stream the message vector to the target destination
  }
}

Clients' View of This Logging

class Log
{
  public static log(String message)
  {
    //One would cache this object in
    //production environments
    ILog ilog = Factory.getInstance().getObject(ILog.NAME);
    ilog.log(message);
  }
}

Usage

void function1()
{
  Log.log("This is a test message");
}

Implementing Filters in Current Servlet Architectures

Filters are only going to be available in 2.3 implementations. But one would quickly realize that even in the current implementations, the majority of developers typically settle for a single base servlet solution anyway. So one could very easily incorporate this solution by making the appropriate calls at the beginning and end of the servlet and effectively acheive the same thing. Carrying this out using filters just seems natural, and also provides an additional opportunity to effect the request and response if needed.

Further Benefitting From Such Models as ADO and TDP

Using the suggested architecture here you have relieved yourself of commits and rollbacks, but you are still dealing with conenctions and other JDBC-related entities. Instead, imagine the following code segment:

void printEmployees()
{
  Hashtable args = new Hashtable();
  args.put("employee_id", "E*");
  ICollection empCollection = DataFactory.executeTransaction("GET_EMPLOYEE",args);
  IIterator empItr = empCollection.getIterator();
  for(empItr.begin();!empItr.atTheEnd();empItr.next())
  {
    Employee emp = (Employee)(empItr.getCurrent();
    System.out.println(emp);
  }
  empCollection.close();
}

Doing it this way, the DataFactory handles all of the connection details. The only thing needed is the abstract collection.close(). Not only that, but the same interface works for non-relational data sources. The rowset API of JDBC 2.0 and the connector architecture, somewhat, have some similarities to these ideas. But the concept and implementation behind this is so simple, one can implement all the abstractions outlined here in less than two weeks. Also, see how the collections actually return objects as opposed to simple rows and columns? Well, if I were to explain the details of this, that would be material for another article.

How Does This Facility Compare to Using EJBs?

One truism in program optimization is that you don't do it pre-maturely. And 80% of your time is spent on 20% of the code. Obviously, one would benefit more by effecting that 20% first. The same way, in an appication server, lots of things happen locally as opposed to distributedly. I wish EJBs addressed these local issues first before addressing the distributed issues; for example, transactions, factory services, security, representing business concepts as entities, and representing business procedures as tasks. These are fundamentally local issues, if you were to make use of them effectively. All of these are of tremendous value to the servlet tier. Becasue EJBs have extended these issues to the network model, the spec is so large that it is harder to follow all the implications. Java has come to be used so effortlessly because it minimizes the complexity of C++, while preserving the OO architecture. I believe the EJB model has grown too complex trying to address multiple constituencies (distributed transactions, object databases, object to relational mapping, etc.). As a result, it is harder to get a good EJB container that is consistent and relatively cheap. My contention is that a lightweight EJB framework has a great value for the servlet tier. And such a framework would have the following features:

In systems developed with the http thin client model, clustering is always possible, either at the client machine level or user session level, providing the needed scale when the user demand increases.

References

Related Reading

Java Servlet Programming, 2nd EditionJava Servlet Programming, 2nd Edition
By Jason Hunter with William Crawford
Table of Contents
Index
Sample Chapter
Full Description

Satya Komatineni is the CTO at Indent, Inc. and the author of Aspire, an open source web development RAD tool for J2EE/XML.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.