Thursday, December 16, 2010

No dynamic filters in servlet spec 2.4 you say?

I had a requirement recently to be able to dynamically control CAS security filters in a web application (default CAS security to off for development and allow it to be turned on by external configuration post-deployment).  Unfortunately, servlet spec 2.4 does not allow one to programatically add new servlet filters (at least that's the prevailing theory).  This is a feature added/being added to the servlet 3.0 API.

My friend Google said there were a number of others who wanted to do the same thing but they were being pointed to servlet 3.0.  Unfortunately, servlet 3.0 and J2EE 6 were not an option for me, so it was looking like a tough nut to crack.

Then it struck me, what if I created a generic, conditional servlet filter that took the name of the class of the real filter as an init param?  And, what if I passed in the condition that was to be evaluated to determine whether or not to create and/or invoke the real filter?  Then, in the conditional filter, I could examine the condition and, as necessary, dynamically create an instance of the wrapped filter class.

Turns out it worked like a charm.  Here's how.  First the filter definition in web.xml:

    CAS Authentication Filter
    my.org.security.servlet.ConditionalFilter
    
        condition
        cas/enabled
    
    
        wrapped-class
        
            org.jasig.cas.client.authentication.AuthenticationFilter
        
    


public class ConditionalFilter implements Filter {

    // instance of the actual filter being wrapped
    private Filter _wrappedFilter;

    // are we to ignore the wrapped filter?
    private boolean _ignore = true;

    public ConditionalFilter() {
    } // constructor

    public void init(FilterConfig filterConfig) throws ServletException {
        // the 'condition' init param tells us whether or not 
        // the wrapped filter is active
        _ignore = !checkCondition(filterConfig.getInitParameter("condition"));

        try {
            if (!_ignore) {
                // the wrapped filter is active so we create an instance 
                // of it and initialize it
                _wrappedFilter = getFilterInstance(
                    filterConfig.getInitParameter("wrapped-class")
                );
                _wrappedFilter.init(filterConfig);
            }
        } catch (Exception e) {
            throw new ServletException(e);
        }
    } // init()

    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain filterChain)
        throws IOException, ServletException {
        if (!_ignore) {
            // the wrapped filter is active so we let it do its work
            _wrappedFilter.doFilter(request, response, filterChain);
        } else {
            // wrapped filter is inactive so simply move on to the next filter
            filterChain.doFilter(request, response);
        }
    }  // doFilter()

    public void destroy() {
        if (_ignore) {
            _wrappedFilter.destroy();
        }
    }  // destroy()

    private Filter getFilterInstance(String className)
        throws ClassNotFoundException, InvalidClassException,
               InvocationTargetException, IllegalAccessException,
               InstantiationException, NoSuchMethodException {
        // try to create an instance of the wrapped filter 
        // with the given class name
        Class filterClass = Class.forName(className);
        java.lang.reflect.Constructor constructor = filterClass.getConstructor();
        Object filter = constructor.newInstance();

        if (!(filter instanceof Filter)) {
            throw new InvalidClassException(
                String.format("'%s' is not an instance of Filter", className)
            );
        }
        return (Filter)filter;
    } // getFilterInstance()

    /*
     * looks up the configured 'condition' via JNDI to determine 
     * whether or not the wrapped filter is active
     */
    private boolean checkCondition(String condition) {
        boolean result = false;

        try {
            InitialContext context = new InitialContext();
            String path = String.format("java:comp/env/%s", condition);
            result =(Boolean)context.lookup(path);
        } catch (final NamingException e) {
            System.out.println(
                "unable to load condition from JNDI"
            );
        }

        return result;
    } // checkCondition()

} // class ConditionalFilter

No comments: