Meeting Plastic II: Simple ChainBuilder

This is a simple implementation of a Chain Of Responsibility or Chain Of Command design pattern. We already have such a service in TapestryIOC and I thought of implementing the same in Plastic.

In Chain of Command design pattern, we create a single service from a set of commands which implement a common interface. This pattern can be provided out of the box by using Class transformations. The assembled service will run all the commands in the given sequence unless any of the commands throw an exception. I am keeping the example simple by forcing the methods in the command interface to return only void.

We start with the ChainBuilder interface.

/**
 * A "Chain Of Responsibility" or "Chain Of Commands" pattern 
 */
public interface ChainBuilder {
   /**
    * Builds a chain instance from a given chain of commands implementing a particular
    * interface
    * @param <T>
    * @param comamndInterface interface type of the command
    * @param commands list of commands
    * @return chain instance
    */
   <T> T build(Class<T> commandInterface, List<T> commands);
}

and now its implementation

/**
 * Implementation of a ChainBuilder interface using Plastic
 */
public class ChainBuilderImpl implements ChainBuilder {
   private PlasticManager pm;

   /**
    * Constructor
    * @param pm plastic manager
    */
   public ChainBuilderImpl(PlasticManager pm) {
      this.pm = pm;
   }

   /**
    * {@inheritDoc}
    */
   @SuppressWarnings("unchecked")
   public <T> T build(Class<T> commandInterface, List<T> commands) {
      // Create a new class implementing this interface
      return (T) pm.createClass(Object.class,
               new ChainBuilderTransformer<T>(commandInterface, commands)).newInstance();
   }

   static public class ChainBuilderTransformer<T> implements PlasticClassTransformer {
      private final Class<T> commandInterface;
      private final List<T> commands;

      public ChainBuilderTransformer(Class<T> commandInterface, List<T> commands) {
         this.commandInterface = commandInterface;
         this.commands = commands;
      }

      public void transform(PlasticClass pc) {
         // Implement the interface
         pc.introduceInterface(commandInterface);

         // Add a field which will be an array containing the commands
         final Object[] arrayOfCommands = commands.toArray();
         final PlasticField arrayOfCommandsField = pc.introduceField(
                   Object[].class, "_commands$"
                  + commandInterface.getSimpleName());
         arrayOfCommandsField.inject(arrayOfCommands);

         // For each method create chain
         for (Method method : commandInterface.getMethods()) {
            createChain(pc, arrayOfCommandsField, method);
         }

      }

      private void createChain(final PlasticClass pc, 
               final PlasticField arrayOfCommandsField,
               final Method method) {
         pc.introduceMethod(method).changeImplementation(
            new InstructionBuilderCallback() {
               public void doBuild(InstructionBuilder builder) {
                  builder.loadThis().getField(arrayOfCommandsField)
                        .iterateArray(new InstructionBuilderCallback() {
                           public void doBuild(InstructionBuilder builder) {
                              builder.loadArguments().invoke(method);
                              return;
                           }
                        });
               builder.returnDefaultValue();
            }
         });
      }
   }
}

The service creates a class by using PlasticManager.createClass using Object.class as the base class and providing it a ChainBuilderTransformer which is an implementation of PlasticClassTransformer.

ChainBuilderTransformer implements the only method transform present in PlasticClassTransformer. It follows the following steps

  1. The command interface is introduced to (implemented by) the newly created class.
  2. A field is created to hold the array of commands and introducted to (inserted into) the newly created class
  3. The array of commands is injected into the field
  4. We loop over the methods in the interface and for each method we create a chain

In createChain we replace the code of this method by calling changeImplementation which takes InstructionBuidlerCallback as argument that has a single method doBuild having InstructionBuilder as argument.

Now comes the tricky part(remember the stack machines and assembly language, that did not go waste!!). What we need to do here is

void myAssembledCommand(arguments){
   for(T command: commands){
      command(arguments);
   }
}

For the loop to execute, it needs the array of commands to be on the top of the stack. In order to get an instance field on top the stack, you have to push the instance on to the stack, then call getField(). We do it by using builder.loadThis().getField(). Once we get the array of commands on the top of stack, we use iterateArray to iterate over the array. iterateArray takes an InstructionBuilderCallback as argument which allows you to generate code for the inside of the loop. In this doBuild() method all we do is invoke tell the builder to load the arguments of the main method from the stack and call the command with those arguments. That is it!! we are done.

Usage

A spock test for using the above ChainBuilder is as under

/**
 * Tests {@link plasticdemo.transforms.ChainBuilderImpl}
 */
class ChainBuilderTest extends Specification {
   def pm

   def setup(){
      pm = PlasticManager.withContextClassLoader().delegate(new StandardDelegate(new RunTransformer())).
         packages(["plasticdemo.controlled"]).create();
   }

   def "test if foo is runnable"(){
      setup:
      def chainBuilder = new ChainBuilderImpl(pm)
      MyService service1 = Mock(MyService)
      MyService service2 = Mock(MyService)
      def chain = chainBuilder.build(MyService, [service1, service2])
      when:
      chain.process()
      then:
      1 * service1.process()
      1 * service2.process()
   }
}

The full source code along with examples from other posts can be found here.

While I was implementing the service I came across the plastic implementation of ChainBuilder which is already implemented in Tapestry Trunk. It takes care of the return value which is very easy. You can follow all the plastic changes in Tapestry5 core here.

Tagged: , ,

One thought on “Meeting Plastic II: Simple ChainBuilder

  1. Howard Lewis Ship April 19, 2011 at 11:45 PM Reply

    The advantage of this approach is the reduction of code provided by using the pattern. In practice, a large number of objects implementing the command interface are aggregated together as a single instance … you can write your tests, mocking up a single command instance, and not have to be concerned with testing the code that does the iteration, or the code that stops iterating when a non-false/null/0 value is returned.

    Further, in Tapestry apps, this represents one of the many ways you can inject your own logic into the middle of Tapestry code: your commands (in the chain) are mixed together with those provided by the framework.

Leave a comment