Using UrlRewriteFilter with the Spring Framework

Alon Salant ·

UrlRewriteFilter is a powerful and widely used Java Servlet Filter for rewriting URLs ala Apache’s mod_rewrite module. We use Spring extensively at Carbon Five and I wanted to use UrlRewriteFilter with Spring-managed configuration and without having to register it in my web.xml file.

Our content management system, SmileMaker, already uses Spring-managed filter chaining and I needed to add UrlRewriteFilter to the mix to give the CMS pretty URLs and to support search engine optimizations (SEO). I achieved this by writing a FilterFactoryBean that will initialize and configure a servlet filter in my Spring application context.

We typically register one Spring FilterToBeanProxy and configure our filters using FilterChainProxy. (Note: these classes referenced are actually part of the ACEGI security framework for Spring.) This keeps the web.xml simple and gives us all the power of Spring for configuring and initializing our filters. In this model, you typically use setter-based injection to configure your filter. In the case of UrlRewriteFilter, the filter must be initialized with the Filter API method init and a FilterConfig object. The problem I needed to solve was how to create and initialize this filter, or any servlet filter really, from Spring where init must be called with initialization parameters in the FilterConfig.

My solution is a fairly standard approach that you use when integrating 3rd party libraries with Spring – create a FactoryBean. The Spring FactoryBean interface provides a simple mechanism for creating objects with life cycle constraints that make them hard to create using Spring’s standard mechanisms.

Here’s my implementation of a FilterFactoryBean. Note that this FactoryBean could be used to initialize any servlet filter in Spring.

package carbonfive.springframework.filter;

import javax.servlet.*;
import java.util.*;

import org.springframework.beans.factory.*;
import org.springframework.web.context.*;
import org.apache.commons.logging.*;

/**
 * FactoryBean implementation for creating and initializing a servlet filter for use
 * in a Spring context. Written to handle initializing UrlRewriteFilter.
 */
public class FilterFactoryBean implements FactoryBean, ServletContextAware, InitializingBean
{
  Log log = LogFactory.getLog(FilterFactoryBean.class);

  private Hashtable initParameters;
  private Class filterClass;
  private ServletContext servletContext;

  public Object getObject() throws Exception
  {
    log.info("Creating filter " + filterClass.getName());

    Filter filter = (Filter) filterClass.newInstance();
    FilterConfig config = new FilterConfigImpl(initParameters);
    filter.init(config);
    return filter;
  }

  public Class getObjectType()
  {
    return Filter.class;
  }

  public boolean isSingleton()
  {
    return true;
  }

  public void setFilterClass(Class filterClass)
  {
    this.filterClass = filterClass;
  }

  public void setInitParameters(Hashtable initParameters)
  {
    this.initParameters = initParameters;
  }

  public void setServletContext(ServletContext servletContext)
  {
    this.servletContext = servletContext;
  }

  public void afterPropertiesSet() throws Exception
  {
    if (filterClass == null) throw new IllegalArgumentException("filterClass can not be null");
    if (!Filter.class.isAssignableFrom(filterClass)) throw new IllegalArgumentException("filterClass must implement javax.servlet.Filter");
  }

  private class FilterConfigImpl implements FilterConfig
  {
    Hashtable properties;

    public FilterConfigImpl(Hashtable properties)
    {
      this.properties = properties;
    }

    public String getFilterName()
    {
      return filterClass.getName() + "-filter";
    }

    public ServletContext getServletContext()
    {
      return servletContext;
    }

    public String getInitParameter(String name)
    {
      return (String) properties.get(name);
    }

    public Enumeration getInitParameterNames()
    {
      return properties.keys();
    }
  }
}

And a test for this implementation using UrlRewriteFilter:

package carbonfive.springframework.filter;

import carbonfive.smilemaker.web.spring.*;
import junit.framework.*;
import org.apache.commons.logging.*;
import org.springframework.mock.web.*;
import org.tuckey.web.filters.urlrewrite.*;

import javax.servlet.*;
import java.util.*;

public class FilterFactoryBeanTest extends TestCase
{
  Log log = LogFactory.getLog(FilterFactoryBeanTest.class);

  public void testCreateUrlRewriteFilter() throws Exception
  {
    log.info("Starting test");

    FilterFactoryBean factory = new FilterFactoryBean();
    factory.setServletContext(new MockServletContext());
    factory.setFilterClass(UrlRewriteFilter.class);

    Properties properties = new Properties();
    properties.put("confPath", "/WEB-INF/rewrite.xml");
    properties.put("confReloadCheckInterval", "30");
    properties.put("logLevel", "DEBUG");
    properties.put("statusPath", "/status");
    properties.put("statusEnabled", "true");
    properties.put("statusEnabledOnHosts", "localhost,127.0.0.1");

    factory.setInitParameters(properties);
    factory.afterPropertiesSet();

    assertEquals(Filter.class, factory.getObjectType());

    Filter filter = (Filter) factory.getObject();
    assertNotNull(filter);
    assertTrue(filter instanceof UrlRewriteFilter);

    UrlRewriteFilter rewriter = (UrlRewriteFilter) filter;
    assertEquals(30, rewriter.getConfReloadCheckInterval());
    assertEquals(true, rewriter.isConfReloadCheckEnabled());
    assertEquals("/status", rewriter.getStatusPath());
    assertEquals(true, rewriter.isStatusEnabled());
  }
}

To create this filter in Spring my configuration looks like:

  <bean id="smilemaker-urlRewriteFilter" class="carbonfive.springframework.filter.FilterFactoryBean">
    <property name="filterClass" value="org.tuckey.web.filters.urlrewrite.UrlRewriteFilter"/>
    <property name="initParameters">
      <props>
        <prop key="confPath">/WEB-INF/config/urlrewrite.xml</prop>
        <prop key="confReloadCheckInterval">10</prop>
        <prop key="statusEnabled">true</prop>
        <prop key="statusPath">/admin/rewrite-status</prop>
      </props>
    </property>
  </bean>

Note that I found and reported (and locally fixed) a bug in UrlRewriteFilter where changes to a config file in a non-standard location do not get picked up.