Kontakt

Advanced Service Builders in Tapestry IoC: Chain of Responsibility Pattern

Posted on Dienstag, 20th Juli, 2010

In the article Advanced Service Builders in Tapestry IoC: Strategy Pattern I described how to implement the Strategy pattern with Tapestry. In this post I’ll cover the Gang of Four Chain of Responsibility Pattern. I’ll start with a simple example that demonstrates how to implement the pattern in Tapestry. At the end of the article, I’ll show you how the Chain of Responsibility Pattern is used inside Tapestry.

The use case

Suppose you need to support several authentication mechanisms in your application. Let’s say you need to support authentication based on both HTTP Basic authentication header (RFC 1945) and HTTP Digest authentication header (RFC 2617). Probably you would specify a service interface like AuthenticationService:

public interface AuthenticationService {
   boolean isAuthenticated();
}

You would also provide two implementations of the service. The implementation details are not interesting yet.

  • HttpBasicAuthenticationService: authentication based on HTTP Basic authentication header
  • HttpDigestAuthenticationService: authentication based on HTTP Digest authentication header

Both implementations can be bound to their interface in the application module. Note that you need to provide the service id to disambiguate both implementations of the AuthenticationService interface.

public class AppModule {

   public static void bind(ServiceBinder binder) {

      binder.bind(AuthenticationService.class,
                  HttpBasicAuthenticationService.class)
            .withId("HttpBasic");
      binder.bind(AuthenticationService.class,
                  HttpDigestAuthenticationService.class)
            .withId("HttpDigest");
   }
}

Naive Approach

Now let’s first have a look at a naive usage of the AuthenticationService service. Let’s implement a simple ComponentRequestFilter which is responsible for recognizing whether an incoming request is authenticated or not.¬† In the following example¬†we need to inject both implementations of AuthenticationService service using the @InjectService annotation. The value of the annotation is used to identify the service to be injected. When a component event request is handled or a page is rendered we check if the request is authenticated by invoking both services HttpBasic and HttpDigest one after another. If the request is unauthenticated, the filter will set the WWW-Authenticate header and send a 401 HTTP error indicating the request requires HTTP authentication.

public class AuthenticationFilter implements ComponentRequestFilter {

   @InjectService("HttpBasic")
   private AuthenticationService basicAuthService;

   @InjectService("HttpDigest")
   private AuthenticationService digestAuthService;

   @Inject
   private Response response;

   public void handleComponentEvent(
         ComponentEventRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handleComponentEvent(parameters);
      }
   }

   public void handlePageRender(
         PageRenderRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handlePageRender(parameters);
      }

   }

   private boolean isAuthenticated() throws IOException {

      if (this.basicAuthService.isAuthenticated()) {
         return true;
      }

      if (this.digestAuthService.isAuthenticated()) {
         return true;
      }

      this.response.setHeader("WWW-Authenticate",
            "Basic realm=\"Tapestry\"");

      this.response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
            "Request unauthorized");

      return false;
   }
}

Injecting both services¬†HttpBasic and HttpDigest and invoking them one after another is not really nice.¬†If you decide to provide a further authentication mechanism in the future, you’ll have to change the AuthenticationFilter class. Imagine you decided to support the authentication based on a Remember-Me-Token. In this case you need to provide a new implementation of AuthenticationService interface¬†which will be injected into AuthenticationFilter using the service id. Now you have three different instances of AuthenticationService that are invoked one after another.

Chain of Responsibility

Now let me show you how to improve our example. We’ll implement the AuthenticationService as a Chain of Responsibility. Instead of providing different implementations of AuthenticationService interface with dfferent service ids, we use the ChainBuilder service to build a chain of AuthenticationService inside a build method.

The ChainBuilder service is used to assemble a Chain Of Command implementation of an interface (the command interface) from an ordered list of objects implementing that interface (the commands). The commands of the chain are contributed to the service configuration.

public class AppModule {

   public static void contributeAuthenticationService(
       OrderedConfiguration<AuthenticationService> config) {

      config.addInstance("HttpBasic",
              HttpBasicAuthenticationService.class);
      config.addInstance("HttpDigest",
              HttpDigestAuthenticationService.class);
   }

   public static AuthenticationService buildAuthenticationService(
         List<AuthenticationService> configuration,
         ChainBuilder chainBuilder) {

      return chainBuilder.build(AuthenticationService.class,
                                configuration);
   }
}

The result is that there is a single instance of AuthenticationService service inside the Registry. You don’t need to disambiguate different instances of the service by providing unique ids and don’t have to care which instance to inject. You just inject the AuthenticationService service and use it. Note that we are not using the @InjectService annotation anymore. Now it is fine to use the¬†@Inject annotation. Tapestry will call the Chain of Command behind the scenes for you. Now AuthenticationFilter can be simplified.

public class AuthenticationFilter implements ComponentRequestFilter {

   @Inject
   private AuthenticationService authenticationService;

   @Inject
   private Response response;

   public void handleComponentEvent(
         ComponentEventRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handleComponentEvent(parameters);
      }
   }

   public void handlePageRender(
         PageRenderRequestParameters parameters,
         ComponentRequestHandler handler) throws IOException {

      if (isAuthenticated()) {
         handler.handlePageRender(parameters);
      }

   }

   private boolean isAuthenticated() throws IOException {

      if (this.authenticationService.isAuthenticated()) {
         return true;
      }

      this.response.setHeader("WWW-Authenticate",
            "Basic realm=\"Tapestry\"");

      this.response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
            "Request unauthorized");

      return false;
   }
}

Now that you know how to create a Chain of Command with Tapestry, let me explain you how Tapestry works down the list of commands.

ChainBuilder Service

As you already learned, the ChainBuilder service creates a Chain Of Command based on a command interface and commands. Behind the scenes Tapestry generates an implementation of the command interface at runtime. For each method in the interface, the chain implementation will call the corresponding method on each command object in turn.  There are several situations when a chain is terminated:

  • If the return type of the interface method is boolean and a command in the chain returned true.
  • If the return type of the interface method is numeric and a command in the chain returned a non-zero value.
  • If the return type of the interface method is other than boolean or numeric and a command in the chain returned a non-null value.
  • If a command in the chain throws an Exception.

Note that in case of void methods only throwing an Exception can terminate the chain.

Now let me explain how chains can be terminated. In the following example you can see HttpBasicAuthenticationService service. It first checks if the Authorization header is set. If the header is not available, then false is returned. In this case the next command in the chain is invoked. If the header is present the command executions continues. If the credentials are available and valid, then true is returned. This terminsates the chain.

public class HttpBasicAuthenticationService implements AuthenticationService {
   @Inject
   private Request request;

   public boolean isAuthenticated() {
      String header = this.request.getHeader("Authorization");

      if (header == null || !header.startsWith("Basic ")) {
         return false;
      }

      String[] tokens = getCredentials(header);

      if (tokens.length != 2) {
         return false;
      }

      return tokens[0].equals("igor")
                  && tokens[1].equals("secret");

   }

   public String[] getCredentials(String header) {
      ...
   }
}

Real Life Example

Now that you know how to implement the Chain Of Command Pattern let me show you how this pattern is used inside Tapestry. As you know Tapestry performs the transformation of the page and component classes at runtime. This bytecode transformation is performed by the ComponentClassTransformWorker service which is implemented as a Chain of Command. The commands of the chain transform a component or page class in turn. For example there is an internal implementation of the  ComponentClassTransformWorker interface which is responsible for transforming a component or a page class in that way that you as page author can inject services into that component or page via @Inject annotation. Another command in the chain is responsible for identifying parameters via the @Parameter annotation on component fields. Almost for every annotation, used in a component or a page, there is a specialized command which is a part of the class transformation chain.

Here is see an extract from TapestryModule:

public class TapestryModule {

    public void contributeComponentClassTransformWorker(
        OrderedConfiguration<ComponentClassTransformWorker>
                     configuration){

        configuration.addInstance("Inject",
                                  InjectWorker.class);
        configuration.addInstance("InjectService",
                                  InjectServiceWorker.class);
        configuration.addInstance("InjectPage",
                                  InjectPageWorker.class);

        ...

        configuration.add("OnEvent", new OnEventWorker());

        ...
        configuration.addInstance("Parameter",
                                  ParameterWorker.class,
                                  "after:Inject*");
   }

   public ComponentClassTransformWorker buildComponentClassTransformWorker(
         List<ComponentClassTransformWorker> configuration,
         ChainBuilder chainBuilder) {

      return chainBuilder.build(
                           ComponentClassTransformWorker.class,
                           configuration);
   }
}

Summary

In this post I described how to implement Chain of Responsibility Pattern using Tapestry IoC. Based on the authentication example you learned how to decouple a sender of a request and its receiver by giving more than one service a chance to handle the request.

In the next post of this series I’ll cover further advance techniques for building services. You’ll learn how to use Shadow services.

 

  1. Inge

 

avatar

Your name

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 

Tapestry 5 Blog - Copyright © 2009 - Eclectic Theme by Your Inspiration Web - Powered by WordPress