Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4535

Introduction into Intercepting Filter Pattern in Java

$
0
0

I just released the Master Class of "Learn Spring Security":

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’re going to introduce the Intercepting Filter Pattern presentation-tier Core J2EE Pattern.

This is the second tutorial in our Pattern Series and a follow-up to the Front Controller Pattern guide which can be found here.

Intercepting Filters are filters that trigger actions before or after an incoming request is processed by a handler.

Intercepting filters represents centralized components in a web application, common to all requests and extensible without affecting existing handlers.

2. Use Cases

Let’s extend the example from the previous guide and implement an authentication mechanism, request logging, and a visitor counter. In addition, we want the ability to deliver our pages in various different encoding.

All these are use cases for intercepting filters because they are common to all requests and should be independent of the handlers.

3. Filter Strategies

Let us introduce different filter strategies and exemplary use cases. To run the code with Jetty Servlet container, simply execute:

$> mvn install jetty:run

3.1. Custom Filter Strategy

The custom filter strategy is used in every use case that requires an ordered processing of requests, in the meaning of one filter is based on the results of a previous filter in an execution chain.

These chains will be created by implementing the FilterChain interface and registering various Filter classes with it.

When using multiple filter chains with different concerns, you can join them together in a filter manager:

Intercepting Filter - Custom Filter Strategy

In our example, the visitor counter is working by counting unique usernames from logged-in users, which means it’s based on the result of the authentication filter, therefore, both filters have to be chained.

Let’s implement this filter chain.

First, we’ll create an authentication filter which checks if the session exists for a set ‘username’ attribute and issue a login procedure if not:

public class AuthenticationFilter implements Filter {
    ...
    @Override
    public void doFilter(
      ServletRequest request,
      ServletResponse response, 
      FilterChain chain) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        
        HttpSession session = httpServletRequest.getSession(false);
        if (session == null || session.getAttribute("username") == null) {
            FrontCommand command = new LoginCommand();
            command.init(httpServletRequest, httpServletResponse);
            command.process();
        } else {
            chain.doFilter(request, response);
        }
    }
    
    ...
}

Now let’s create the visitor counter. This filter maintains a HashSet of unique usernames and adds a ‘counter’ attribute to the request:

public class VisitorCounterFilter implements Filter {
    private static Set<String> users = new HashSet<>();

    ...
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) {
        HttpSession session = ((HttpServletRequest) request).getSession(false);
        Optional.ofNullable(session.getAttribute("username"))
          .map(Object::toString)
          .ifPresent(users::add);
        request.setAttribute("counter", users.size());
        chain.doFilter(request, response);
    }

    ...
}

Next, we’ll implement a FilterChain that iterates registered filters and executes doFilter method:

public class FilterChainImpl implements FilterChain {
    private Iterator<Filter> filters;

    public FilterChainImpl(Filter... filters) {
        this.filters = Arrays.asList(filters).iterator();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
        if (filters.hasNext()) {
            Filter filter = filters.next();
            filter.doFilter(request, response, this);
        }
    }
}

To wire our components together, let’s create a simple static manager which is responsible for instantiating filter chains, registering its filters, and initiate it:

public class FilterManager {
    public static void process(HttpServletRequest request,
      HttpServletResponse response, OnIntercept callback) {
        FilterChain filterChain = new FilterChainImpl(
          new AuthenticationFilter(callback), new VisitorCounterFilter());
        filterChain.doFilter(request, response);
    }
}

As the last step we’ll have to call our FilterManager as common part of the request processing sequence from within our FrontCommand:

public abstract class FrontCommand {
    ...

    public void process() {
        FilterManager.process(request, response);
    }

    ...
}

3.2. Base Filter Strategy

In this section, we’ll present the Base Filter Strategy, with which a common superclass is used for all implemented filters.

This strategy plays nicely together with the custom strategy from the previous section or with the Standard Filter Strategy that we’ll introduce in the next section.

The abstract base class can be used to apply custom behavior that belongs to a filter chain. We’ll use it in our example to reduce boilerplate code related to filter configuration and debug logging:

public abstract class BaseFilter implements Filter {
    private Logger log = LoggerFactory.getLogger(BaseFilter.class);

    protected FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("Initialize filter: {}", getClass().getSimpleName());
        this.filterConfig = filterConfig;
    }

    @Override
    public void destroy() {
        log.info("Destroy filter: {}", getClass().getSimpleName());
    }
}

Let’s extend this base class to create a request logging filter, which will be integrated into the next section:

public class LoggingFilter extends BaseFilter {
    private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(
      ServletRequest request, 
      ServletResponse response,
      FilterChain chain) {
        chain.doFilter(request, response);
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        
        String username = Optional
          .ofNullable(httpServletRequest.getAttribute("username"))
          .map(Object::toString)
          .orElse("guest");
        
        log.info(
          "Request from '{}@{}': {}?{}", 
          username, 
          request.getRemoteAddr(),
          httpServletRequest.getRequestURI(), 
          request.getParameterMap());
    }
}

3.3. Standard Filter Strategy

A more flexible way of applying filters is to implement the Standard Filter Strategy. This can be done by declaring filters in a deployment descriptor or, since Servlet specification 3.0, by annotation.

The standard filter strategy allows to plug-in new filters into a default chain without having an explicitly defined filter manager:

Intercepting Filter - Standard Filter Strategy

Note that the order, in which the filters get applied, cannot be specified via annotation. If you need an ordered execution, you have to stick with a deployment descriptor or implement a custom filter strategy.

Let’s implement an annotation driven encoding filter that also uses the base filter strategy:

@WebFilter(servletNames = {"intercepting-filter"}, 
  initParams = {@WebInitParam(name = "encoding", value = "UTF-8")})
public class EncodingFilter extends BaseFilter {
    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        super.init(filterConfig);
        this.encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain) {
        String encoding = Optional
          .ofNullable(request.getParameter("encoding"))
          .orElse(this.encoding);
        response.setCharacterEncoding(encoding); 
        
        chain.doFilter(request, response);
    }
}

In a Servlet scenario with having a deployment descriptor, our web.xml would contain these extra declarations:

<filter>
    <filter-name>encoding-filter</filter-name>
    <filter-class>
      com.baeldung.patterns.intercepting.filter.filters.EncodingFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>encoding-filter</filter-name>
    <servlet-name>intercepting-filter</servlet-name>
</filter-mapping>

Let’s pick-up our logging filter and annotate it too, in order to get used by the Servlet:

@WebFilter(servletNames = "intercepting-filter")
public class LoggingFilter extends BaseFilter {
    ...
}

3.4. Template Filter Strategy

The Template Filter Strategy is pretty much the same as the base filter strategy, except that it uses template methods declared in the base class that must be overridden in implementations:

Intercepting Filter - Template Filter Strategy

Let’s create a base filter class with two abstract filter methods that get called before and after further processing.

Since this strategy is less common and we don’t use it in our example, a concrete implementation and use case is up to your imagination:

public abstract class TemplateFilter extends BaseFilter {
    protected abstract void preFilter(HttpServletRequest request,
      HttpServletResponse response);

    protected abstract void postFilter(HttpServletRequest request,
      HttpServletResponse response);

    @Override
    public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        
        preFilter(httpServletRequest, httpServletResponse);
        chain.doFilter(request, response);
        postFilter(httpServletRequest, httpServletResponse);
    }
}

4. Conclusion

The Intercepting Filter Pattern captures cross-cutting concerns that can evolve independently of the business logic. From the perspective of business operations, filters are executed as a chain of pre or post actions.

As we’ve seen so far, the Intercepting Filter Pattern can be implemented using different strategies. In a ‘real world’ applications these different approaches can be combined.

As usual, you’ll find the sources on GitHub.

I just released the Master Class of "Learn Spring Security" Course:

>> CHECK OUT THE COURSE


Viewing all articles
Browse latest Browse all 4535

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>