33

I'd like the ability to modify/configure filters in a different way than web.xml. Here is a static configuration of 2 filters. I'd like the ability to have one filter statically configured and allow that filter to load additional filters. I just wanted to know if anyone knows of lib that already has this.

Using Servlet API 2.5

<web-app>
  ...
  <filter>
    <filter-name>MyFilter1</filter-name>
    <filter-class>com.me.MyFilter1</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>MyFilter1</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  ...
  <filter>
    <filter-name>MyFilter2</filter-name>
    <filter-class>com.me.MyFilter2</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>MyFilter2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  ...
</web-app>

I've seen this done in Guice with GuiceFilter where the Filters are configured at runtime.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
TJR
  • 3,617
  • 8
  • 38
  • 41
  • It would be dependent of the servlet container, so you should tell us which one you are using – SJuan76 Aug 25 '11 at 15:22
  • Must it be dependent? Is GuiceFilter dependent on the container? – TJR Aug 25 '11 at 15:39
  • Guice rolls its own mapping mechanism that behaves just like `web.xml` mappings -- for the web container, all requests end at `GuiceFilter`. If you want Guice, just use it :) – Philipp Reichart Sep 27 '11 at 17:32

4 Answers4

41

Just do the same job as the container already does. I.e. reinvent the wheel of the chain of responsibility design pattern as is under the covers been used by servlet filters.

public class GodFilter implements Filter {

    private Map<Pattern, Filter> filters = new LinkedHashMap<Pattern, Filter>();

    @Override
    public void init(FilterConfig config) throws ServletException {
        Filter1 filter1 = new Filter1();
        filter1.init(config);
        filters.put(new Pattern("/foo/*"), filter1);

        Filter2 filter2 = new Filter2();
        filter2.init(config);
        filters.put(new Pattern("*.bar"), filter2);

        // ...
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest hsr = (HttpServletRequest) request;
        String path = hsr.getRequestURI().substring(hsr.getContextPath().length());
        GodFilterChain godChain = new GodFilterChain(chain);

        for (Entry<Pattern, Filter> entry : filters.entrySet()) {
            if (entry.getKey().matches(path)) {
                godChain.addFilter(entry.getValue());
            }
        }

        godChain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        for (Filter filter : filters.values()) {
            filter.destroy();
        }
    }

}

with those little helper classes (which can if necessary be made private static nested classes of the above GodFilter):

public class Pattern {

    private int position;
    private String url;

    public Pattern(String url) {
        this.position = url.startsWith("*") ? 1
                      : url.endsWith("*") ? -1
                      : 0;
        this.url = url.replaceAll("/?\\*", "");
    }

    public boolean matches(String path) {
        return (position == -1) ? path.startsWith(url)
             : (position == 1) ? path.endsWith(url)
             : path.equals(url);
    }

}

and

public class GodFilterChain implements FilterChain {

    private FilterChain chain;
    private List<Filter> filters = new ArrayList<Filter>();
    private Iterator<Filter> iterator;

    public GodFilterChain(FilterChain chain) {
        this.chain = chain;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (iterator == null) {
            iterator = filters.iterator();
        }

        if (iterator.hasNext()) {
            iterator.next().doFilter(request, response, this);
        } else {
            chain.doFilter(request, response);
        }
    }

    public void addFilter(Filter filter) {
        if (iterator != null) {
            throw new IllegalStateException();
        }

        filters.add(filter);
    }

}

You could if necessary also feed a XML config file with all possible filters so that you end up with easier configuration. You could use reflection to create filters in init() of your GodFilter.

Oh nevermind, that's what the web.xml and the container already is doing...

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Shouldn't GodFilterChain.doFilter() loop over all added filters than only use the first? – MRalwasser Aug 13 '12 at 16:02
  • @MRalwasser: Uh, no. Perhaps you're missing how "Chain of responsibility" design pattern works? The `chain.doFilter()` call in the filter's implementation would invoke the `GodFilterChain#doFilter()` again (because it's as `this` been passed as `FilterChain` argument) and then the iterator would advance to the next element, etcetera. – BalusC Aug 13 '12 at 16:05
  • ah, I totally missed that subsequent iterator.next() invocations will be done inside deeper "chain levels". Thanks, for pointing this out. – MRalwasser Aug 13 '12 at 16:30
  • It works great! But could you please explain a use of Pattern? Why we need replace /* and hold position? – Abylay Sabirgaliyev Oct 20 '14 at 06:12
  • 1
    @user99560: so that the very same code doesn't unnecessarily need to be executed everytime. The result is the same everytime, so it's logically more efficient to calculate it once and get hold of it for reuse. – BalusC May 21 '15 at 22:36
  • 1
    these are the reasons you have 629k. this is impressive and clean. +1 – natedennis Feb 10 '16 at 22:04
  • @BalusC, this might be another question to post, but is there a way to configure filters in a different xml file other than defining them in web.xml? – Agent47 Mar 21 '20 at 18:24
18

Servlet 3.0 has the @WebFilter annotation to define a filter. No need to declare it in web.xml anymore.

But loading a filter from a filter is not supported. You could implement it yourself: it's "just" the chain of responsibility pattern, but why would you?

Flexo
  • 87,323
  • 22
  • 191
  • 272
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • 2
    Deleted my answer, beat me to it by 30 seconds >:| – Dave Aug 25 '11 at 15:32
  • 2
    @JB Nizet : TJR is not asking for version servlet 3.0, he is specific with servlet 2.5 – developer Aug 25 '11 at 15:42
  • @JB, I've added to the question that I'm using 2.5. That 3.0 feature does make me happy though. – TJR Aug 25 '11 at 15:46
  • 1
    What is was looking for `@WebFilter(filterName = "customFilter", urlPatterns = { "/*" }) public class CustomFilter implements Filter ....` does it – absin Feb 22 '18 at 11:39
4

It can be achieved in easy steps, even for pre-3.0 Servlet spec:

  1. Add a filter containing a static & ordered collection of Classes (chain).
  2. Map the filter to intercept every traffic.
  3. Manipulate the order & existence of your helper classes (those will be called privately by your filter upon interception of traffic) in the chain.

Ref: Xstream uses same kind of pattern for Serializer, well not with Servlet/Filter though. :)

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
Puspendu Banerjee
  • 2,631
  • 16
  • 19
1

I personally like the @WebFilter annotation to register servlet filters.
But another solution is to add the filter at runtime using the ServletContext's addFilter function.

Implement ServletContextListener; something like:

public class MyContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent ce) {
        ServletContext servletContext = ce.getServletContext();
    
        // you can even conditionally add this
        servletContext.addFilter("My filter 1", MyFilter1.class)
                .addMappingForUrlPatterns(allOf(DispatcherType.class), false, "/*");
    }
}

Register listener:

<listener>
    <listener-class>com.me.MyContextListener</listener-class>
</listener>   

And of course you need to implement a Filter. But in your question you already refer to an example filter 'MyFilter1'.

R. Oosterholt
  • 7,720
  • 2
  • 53
  • 77