Servlet Filter in OSGi enabled web application

Servlet filters are an important component of web application and it can be used in different scenarios, e.g.:

1) Authenticating requests (ServletRequest) before delegating them to your actual business class (Servlet/Controller).
2) Formatting request (ServletRequest) and response data (ServletResponse)
3) Global/Application level Error handling

There are many more uses cases where we can use Servlet filters.
A traditional web application is composed of couple of Business classes & data (model), JSP (views), controllers/servlets and few other resources like css, images etc. All these components are packaged as a WAR file and we deploy it on a web/application server. To add/configure a servlet filter in traditional web application we user web.xml configuration file and tag as:
 
  springSecurityFilterChain
  org.springframework.web.filter.DelegatingFilterProxy
 

 
     springSecurityFilterChain
     /*
     REQUEST
     ERROR
 
       


An OSGi web application is different as compared to traditional web applications. Below are few major differences:

1) Traditional web applications are deployed on web/application server and run under a servlet container whereas OSGi web application are deployed in an OSGi container that has servlet container as an OSGi HTTP service.
2) In traditional web application required libraries/jars are part of WAR file whereas in OSGi web application jar files are installed as OSGi bundles in OSGi container.
3) In traditional web application Filter are configured using web application whereas in OSGi web application Filter are configured/registered using OSGi HTTP service.

As I mentioned, in OSGi web application we don’t configure filters in web.xml file rather we need to register it through OSGi HTTP service, there are two different ways to do this:

1) Create a Filter and register it as an OSGi service in to OSGi HTTP service (ExtHttpService)
2) Create a Filter and make it as an OSGi component (Whiteboard method)
In this post we are going to create a simple filter to encode all request parameters using UTF-8 encoding before passing that request to a servlet/controller. We have used this concept in Adobe/Day CQ5 that uses Apache Felix as OSGi framework for managing OSGi components and services.

1) Create a Filter and register it to OSGi HTTP service
a) Create a class that implements javax.servlet.Filter and add you logic to doFilter() method. Also, if required implement the init() and destroy() to do some initialization and cleanup activities.
public class CharacterEncodingFilter implements Filter {
 private String encoding = "UTF-8";
 private Boolean forceEncoding = true;
 public void init(FilterConfig filterConfig) throws ServletException {
  try {
   String encoding = filterConfig.getInitParameter("init.charencoding.filter.encoding");
   if(encoding != null && encoding.trim().length() >0) {
    this.encoding = encoding;

   }

   if(filterConfig.getInitParameter("init.charencoding.filter.forceencoding") != null) {
    Boolean forceEncoding = Boolean.parseBoolean(
      filterConfig.getInitParameter("init.charencoding.filter.forceencoding"));
    this.forceEncoding = forceEncoding;

   }
  } catch (Exception ex) {
   ex.printStackTrace();
  }
 }

 public void doFilter(ServletRequest request, ServletResponse response, 
   FilterChain chain) throws IOException, ServletException {
  ExtraParamWrappedRequest reqwrapper = new ExtraParamWrappedRequest((HttpServletRequest) request, null);

  if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
   Map additionalParams = new HashMap();
   additionalParams.put("_charset_", new String[]{encoding});
   reqwrapper = new ExtraParamWrappedRequest((HttpServletRequest) request, additionalParams);
   reqwrapper.setCharacterEncoding(this.encoding);

   if (this.forceEncoding) {
    response.setCharacterEncoding(this.encoding);
   }

  }

  chain.doFilter(reqwrapper, response);
 }

 public void destroy() {
  // TODO Auto-generated method stub
 }

 class ExtraParamWrappedRequest extends HttpServletRequestWrapper {
  private final Map modifiableParameters;
  private Map allParameters = null;

  public ExtraParamWrappedRequest(final HttpServletRequest request, final Map additionalParams) {
   super(request);
   modifiableParameters = new TreeMap();

   if(additionalParams != null) {
    modifiableParameters.putAll(additionalParams);
   }

  }

  @Override
  public String getParameter(final String name) {
   String strings = getRequest().getParameter(name);

   if (strings != null)  {
    return strings;
   }
   return null;
  }

  @SuppressWarnings("unchecked")
  @Override
  public Map getParameterMap() {
   if (allParameters == null) {
    allParameters = new TreeMap();
    allParameters.putAll(super.getParameterMap());
    allParameters.putAll(modifiableParameters);
   }
   return Collections.unmodifiableMap(allParameters);
  }

  @Override
  public Enumeration getParameterNames() {
   return Collections.enumeration(getParameterMap().keySet());

  }

  @Override
  public String[] getParameterValues(final String name) {
   return getParameterMap().get(name);
  }

 }

}

b) Every OSGi bundle has an Activator class, we need to register the filter that we have created in step (a) to OSGi HTTP service in our Activator class by getting the reference of HTTP service (ExtHttpService).

public class Activator implements BundleActivator {
 private static final Logger log = LoggerFactory.getLogger(Activator.class);

 public void start(BundleContext context) throws Exception {
  ServiceReference sRef = context.getServiceReference(ExtHttpService.class.getName());

  if (sRef != null) {
   Dictionary properties = new Hashtable();
   properties.put("service.pid", CharacterEncodingFilter.class.getName());
   properties.put("init.charencoding.filter.encoding", "UTF-8");
   properties.put("init.charencoding.filter.forceencoding", "true");
   ExtHttpService service = (ExtHttpService) context.getService(sRef);
   service.registerFilter(new CharacterEncodingFilter(), "/.*", properties, 0, null);
  }
 }

 public void stop(BundleContext context) throws Exception {
  System.out.println(context.getBundle().getSymbolicName() + " stopped");
 }

}


2) Create a Filter and make it as an OSGi component

Second method of registering a filter to OSGi container is, creating it as a OSGi component/service. Below are the steps that we need to follow:

a) Create a class that implements javax.servlet.Filter and add you logic to doFilter() method. Also, if required implement the init() and destroy() to do some initialization and cleanup activities. Basically, we can use the same class (CharacterEncodingFilter) that we have created above, the only difference we have in this method is the way of registering a filter.

b) Get hold of OSGi bundle context and register filter as a service as shown below:
public class Activator implements BundleActivator {
 private ServiceRegistration registration;
 public void start(BundleContext context) throws Exception {
  Hashtable props = new Hashtable();
  props.put("pattern", /.*");
    props.put("init.message", "Character encoding filter!");
  props.put("service.ranking", "1");
  this.registration = context.registerService(Filter.class.getName(), new CharacterEncodingFilter (), props);
 }

 public void stop(BundleContext context) throws Exception {
  this.registration.unregister();
 }

}

We have seen two different ways of registering filter for a web application that is running under OSGi container so, what’s the difference between these two methods?

An OSGi container may contain multiple OSGi web application and each web application runs under its own bundle context. HTTP service is the service that manages HTTP request/response for all web application deployed under an OSGi container. First method registers filter at HTTP service level (global) and is applied to all request/response across all web application deployed in an OSGi container whereas second method registers a filter at bundle (individual web application) level and gets only executed only when a request is for that particular application/bundle. When we have multiple filters, they can be positioned based on need (which one to execute first and after that and so on) at HTTP service (fourth argument of service.registerFilter method) and individual application bundle level (service.ranking property).
I hope this will help you to understand how Servlets filters works in OSGi enabled web applications.

References:
http://felix.apache.org/site/apache-felix-http-service.html
http://www.eclipse.org/equinox/server/


Thanks
-- Surya

Comments

Anonymous said…
I was trying to do the same in a Glassfish, but I got no luck. I couldn't connect a filter to the JaxRs(Glassfish Jersey) web service which is a WAB. WAB just ignores the filter.
May be you have some ideas?
robson said…
Thanks for this tutorial. It is the clearest decription of the work with filters in OSGi I've read yet,

Popular posts from this blog

Sling Authentication

CQ Development - OSGi bundles and Components

Multiple log files using log4j appender

Create an AEM (CQ) project using Maven