hlship      2003/09/11 13:52:05

  Modified:    hivemind/xdocs navigation.xml
  Added:       hivemind/xdocs interceptors.xml
  Log:
  Add documentation on creating new interceptors.
  
  Revision  Changes    Path
  1.19      +2 -1      jakarta-commons-sandbox/hivemind/xdocs/navigation.xml
  
  Index: navigation.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/hivemind/xdocs/navigation.xml,v
  retrieving revision 1.18
  retrieving revision 1.19
  diff -u -r1.18 -r1.19
  --- navigation.xml    10 Sep 2003 16:54:52 -0000      1.18
  +++ navigation.xml    11 Sep 2003 20:52:05 -0000      1.19
  @@ -23,6 +23,7 @@
                        <item name="Localization" href="/localization.html"/>
                        <item name="Inversion of Control" href="/ioc.html"/>
                        <item name="Multi-Threading" href="/multithreading.html"/>
  +                     <item name="Creating New Interceptors" 
href="/interceptors.html"/>
                        <item name="Overriding Services" href="/override.html"/>
                        <item name="Case Study #1: Application Startup/Shutdown"
                                href="case1.html"/>
  
  
  
  1.1                  jakarta-commons-sandbox/hivemind/xdocs/interceptors.xml
  
  Index: interceptors.xml
  ===================================================================
  <?xml version="1.0"?>
  <!-- $Id: interceptors.xml,v 1.1 2003/09/11 20:52:05 hlship Exp $ -->
  <!DOCTYPE document [
        <!ENTITY % common-links SYSTEM "../common/links.xml">
        %common-links;
        ]>
  <document>
        <properties>
                <title>Creating New Interceptors</title>
                <author email="[EMAIL PROTECTED]">Howard M. Lewis Ship</author>
        </properties>
        <body>
                <section name="Introduction">
  
  <p>
  Interceptors are used to add behavior to a HiveMind service after the fact.  An 
interceptor sits between
  the client code and the core service implementation; it implements the service 
interface. For each method
  in the service interface, the interceptor will re-invoke the method on the
  next object in the chain ... either another interceptor, or the core service 
implementation.  
  </p>
  
  <p>
  That's not useful ... but when the interceptor does something before and/or after 
re-invoking the
  method, it can easily add quite a bit of useful, robust functionality.
  </p>
  
  <p>
  In fact, if you've heard about "Aspect Oriented Programming", interceptors are 
simply one kind of aspect, a 
  method introduction, based on service interface.      
  </p>
  
  <p>
  Be warned; interceptors are an example of programs writing other programs; it's a 
whole new level of abstraction and
  requires a bit of getting used to.    
  </p>
  
  </section>
  
  <section name="Interceptor Factories">
        
  <p>
  Interceptors are created, at runtime, by interceptor factories.  An interceptor 
factory builds a custom class at runtime
  using the Javassist library. The class is then instantiated.  
  </p>
  
  <p>
  Interceptor factories are HiveMind services which implement the
  <a 
href="apidocs/org/apache/commons/hivemind/ServiceInterceptorFactory.html">ServiceInterceptorFactory</a>
    
  interface. This interface has a single method, <code>createInterceptor()</code>, 
which is passed:
  <ul>
  <li>The <a 
href="apidocs/org/apache/commons/hivemind/InterceptorStack.html">InterceptorStack</a> 
(an object used to manage the process of creating interceptors for a service)</li>
  <li>The <a href="apidocs/org/apache/commons/hivemind/Module.html">Module</a> which 
invoked the interceptor factory</li>
  <li>A list of parameters</li>
  </ul>
  </p>
        
  <p>
  Like service implementation factories, interceptor factories may take parameters; 
they may define a &parameters-schema;
  which is used to convert any XML enclosed by the &interceptor; element into Java 
objects.     Many interesting
  interceptors can be created without needing parameters to guide the fabrication of 
the interceptor class.
  </p>  
  
  </section>
  
  <section name="Implementing the NullInterceptor">
  
  <p>
  To demonstrate how easy it is to create an interceptor, we'll start with a 
NullInterceptor.  NullInterceptor
  does not add any functionality, it simply re-invokes each method on its 
<em>inner</em>.  The <em>inner</em> is
  the next interceptor, or the core service implementation ... an interceptor doesn't 
know or care which.
  </p>
  
  <p>
  Simple interceptors, those which do not take any parameters, are implemented by 
subclassing
  <a 
href="apidocs/org/apache/commons/hivemind/service/impl/AbstractServiceInterceptorFactory.html">AbstractServiceInterceptorFactory</a>.
  It does most of the work, organizing the process of creating the class and methods 
... even adding a
  <code>toString()</code> method implementation automatically.
  </p>
  
  <subsection name="NullInterceptor Class">
  
  <p>
  Most of the work for creating a standard service interceptor factory is taken care 
of by the AbstractServiceInterceptorFactory
  base class.   All that's left is to define what happens for each method in the 
service interface.
  
  <source><![CDATA[
  package com.example.impl;
  
  import java.lang.reflect.Modifier;
  
  import org.apache.commons.hivemind.service.BodyBuilder;
  import org.apache.commons.hivemind.service.ClassFab;
  import org.apache.commons.hivemind.service.impl.AbstractServiceInterceptorFactory;
  
  public class NullInterceptor extends AbstractServiceInterceptorFactory
  {
  
      protected void addServiceMethodImplementation(
          ClassFab classFab,
          String methodName,
          Class returnType,
          Class[] parameterTypes,
          Class[] exceptionTypes)
      {
          BodyBuilder builder = new BodyBuilder();
  
          builder.begin();
          builder.add("return ($r) _inner.");
          builder.add(methodName);
          builder.add("($$);");
          builder.end();
  
          classFab.addMethod(
              Modifier.PUBLIC,
              methodName,
              returnType,
              parameterTypes,
              exceptionTypes,
              builder.toString());
      }
  
  }
  ]]></source>
  </p>  
  
  <p>
  The <code>addServiceMethodImplementation()</code>      method is invoked for each 
service method.
  It is passed the
  <a href="apidocs/org/apache/commons/hivemind/service/ClassFab.html">ClassFab</a>, an 
object which represents a class being fabricated, which allows new fields, methods and 
constructors
  to be added.
  </p>
  
  <p>
  ClassFab and friends are just a wrapper around the Javassist framework, a library 
used for runtime bytecode enhancement and
  other aspect oriented programming tasks.  HiveMind uses only a small fraction of the 
capabilities of Javassist.
  Javassist's greatest feature is how new code is specified ... as enhanced Java code!
  </p>
  
  <p>
  The 
  <a 
href="apidocs/org/apache/commons/hivemind/service/BodyBuilder.html">BodyBuilder</a>    
    class helps
  with assembling method bodies in bits and pieces.  The <code>_inner</code> variable 
is a private instance variable,
  the inner for this interceptor.  The <code>($r)</code> reference means "cast to the 
return type for this method", and properly handles
  void methods.  The <code>$$</code> is a placeholder for a comma-seperated list of 
all the parameters to the method.
  </p>
  
  <p>AbstractServiceInterceptorFactory is responsible for creating the 
<code>_inner</code> variable and
        building the constructor which sets it, as well as invoking the constructor on 
the
        completed interceptor class.
        </p>
        
  </subsection>
  
  <subsection name="Declaring the Service">
  
  <p>
  To use a service, it is necessary to declare the service in a module deployment 
descriptor.  The
  AbstractServiceInterceptorFactory expects two properties to be set when the service 
is constructed,
  <code>extensionId</code> and <code>factory</code>:
  
  <source><![CDATA[
  
        <service id="NullInterceptor" 
interface="org.apache.commons.hivemind.ServiceInterceptorFactory">
                <invoke-factory service-id="hivemind.BuilderFactory">
                  <construct class="com.example.impl.NullInterceptor"
                        point-id-property="extensionId">
                        <set-service property="factory" 
service-id="hivemind.ClassFactory"/>
                  </construct>  
                </invoke-factory>
        </service>
  
  
  ]]></source>
  </p>
        
  </subsection>
        
  </section>
  
  <section name="Implementing the hivemind.LoggingInterceptor service">
        
  <p>
  A more involved example is the LoggingInterceptor service, which adds logging 
capabilities to
  services.     It's a bit more involved than NullInterceptor, and so overrides more 
methods of
  AbstractServiceInterceptorFactory.
  </p>
  
  <subsection name="AbstractLoggingInterceptor base class">
  <p>
  In most cases, an abstract base class for the interceptor is provided; in this case, 
it is
  <a 
href="apidocs/org/apache/commons/hivemind/service/impl/AbstractLoggingInterceptor.html">AbstractLoggingInterceptor</a>.
  This class provides several protected methods used by fabricated interceptors.  To 
help ensure
  that there are no conflicts between the method of the service interface and the 
methods
  provided by the super-class, the provided methods are named with a leading 
underscore.  These methods are:
  <ul>
  <li><code>_logEntry()</code> to log entry to a method</li>    
  <li><code>_logExit()</code> to log exit from a method, with return value</li>
  <li><code>_logVoidExit()</code> to log exit from a void method (no return value)</li>
  <li><code>_logException()</code> to log an exception thrown when the method is 
executed</li>
  <li><code>_isDebugEnabled()</code> to determine if debugging is enabled or 
disabled</li>
  </ul>
  </p>
  
  <p>
  In addition, there's a protected constructor, which takes an instance of 
<code>org.apache.commons.logging.Log</code>
  that must be invoked from the fabricated subclass.
  </p>
  
  <p>
  Method <code>getInterceptorSuperclass()</code>         is used to tell
  AbstractServiceInterceptorFactory which class to use as the base:
  <source>
      protected Class getInterceptorSuperclass()
      {
          return AbstractLoggingInterceptor.class;
      }
  </source>
  </p>
  
  </subsection>
  
  <subsection name="Creating the infrastructure">
        
  <p>
  The method <code>createInfrastructure()</code> is used to add fields and 
constructors to the
  interceptor class.
  <source>
      protected void createInfrastructure(InterceptorStack stack, ClassFab classFab)
      {
          Class topClass = stack.peek().getClass();
  
          classFab.addField("_inner", topClass);
  
          classFab.addConstructor(
              new Class[] { Log.class, topClass },
              null,
              "{ super($1); _inner = $2; }");
      }
  </source>
  </p>
  
  <p>
  Since, when a interceptor is created, the inner object has already been created, we 
can
  use its <em>actual type</em> for the <code>_inner</code> field. This results in a 
much more
  efficient method invocation than if <code>_inner</code>'s type was the service 
interface.
  </p>
        
  
  </subsection>
  
  <subsection name="Instantiating the Instance">
  
  <p>
  The method <code>instantiateInterceptor()</code> is used to create a new instance
  from the fully fabricated class.
  <source>
      protected Object instantiateInterceptor(InterceptorStack stack, Class 
interceptorClass)
          throws Exception
      {
          Object stackTop = stack.peek();
          Class topClass = stackTop.getClass();
  
          Log log = 
LogFactory.getLog(stack.getServiceExtensionPoint().getExtensionPointId());
  
          Constructor c = interceptorClass.getConstructor(new Class[] { Log.class, 
topClass });
  
          return c.newInstance(new Object[] { log, stackTop });
      } 
  </source>
  </p>
  
  <p>
  This implementation gets the top object from the stack (the inner object for this
  interceptor) and the correct <code>Log</code>  instance (based on the service 
extension point id ...
  for the service being extended with the interceptor).  The constructor, created
  by <code>createInfrastructure()</code> is accessed and invoked to create the 
interceptor.
  </p>
        
  </subsection>
  
  <subsection name="Adding a Service Methods">
  
  <p>
  The last, and most complex, part of this is the       method which actually creates 
each
  service method.
  
  <source><![CDATA[
      protected void addServiceMethodImplementation(
          ClassFab classFab,
          String methodName,
          Class returnType,
          Class[] parameterTypes,
          Class[] exceptions)
      {
          boolean isVoid = (returnType == void.class);
  
          BodyBuilder builder = new BodyBuilder();
  
          builder.begin();
          builder.addln("boolean debug = _isDebugEnabled();");
  
          builder.addln("if (debug)");
          builder.add("  _logEntry(");
          builder.addQuoted(methodName);
          builder.addln(", $args);");
  
          if (!isVoid)
          {
              builder.add(ClassFabUtils.getJavaClassName(returnType));
              builder.add(" result = ");
          }
  
          builder.add("_inner.");
          builder.add(methodName);
          builder.addln("($$);");
  
          if (isVoid)
          {
              builder.addln("if (debug)");
              builder.add("  _logVoidExit(");
              builder.addQuoted(methodName);
              builder.addln(");");
          }
          else
          {
              builder.addln("if (debug)");
              builder.add("  _logExit(");
              builder.addQuoted(methodName);
              builder.addln(", ($w)result);");
              builder.addln("return result;");
          }
  
          builder.end();
  
          MethodFab methodFab =
              classFab.addMethod(
                  Modifier.PUBLIC,
                  methodName,
                  returnType,
                  parameterTypes,
                  exceptions,
                  builder.toString());
  
          builder.clear();
  
          builder.begin();
          builder.add("_logException(");
          builder.addQuoted(methodName);
          builder.addln(", $e);");
          builder.addln("throw $e;");
          builder.end();
  
          String body = builder.toString();
  
          int count = exceptions == null ? 0 : exceptions.length;
  
          for (int i = 0; i < count; i++)
          {
              methodFab.addCatch(exceptions[i], body);
          }
  
          // Catch and log any runtime exceptions, in addition to the
          // checked exceptions.
  
          methodFab.addCatch(RuntimeException.class, body);
      }
  ]]></source>
  </p>  
        
  <p>
  When you implement logging in your own classes, you often invoke the
  method <code>Log.isDebugEnabled()</code> multiple times ... but in the fabricated 
class,
  the method is only invoked once and cached for the duration of the call ... a little 
efficiency gained back.
  </p>
  
  <p>
  Likewise, if a method can throw an exception or return from the middle, its hard to 
be assured that you've logged
  every exit, or overy thrown exception; taking this code out into an interceptor 
class ensures that
  its done consistently and properly.   
  </p>
        
  </subsection>
  
  </section>
  
  <section name="Implementing Interceptors with Parameters">
  
  <p>
  Interceptor factories may take parameters ... but then their implementation can't be 
based
  on AbstractServiceInterceptorFactory. The unit tests for HiveMind includes an 
  <a 
href="xref-test/hivemind/test/services/impl/FilterLoggingInterceptor.html">example</a> 
of
  such a factory. The basic approach is the same ... you just need a little extra work 
to
  validate, interpret and use the parameters.
  </p>
  
  <p>
  When would such as thing be useful?  One example is declarative security; you could 
specify, on a
  method-by-method basis, which methods were restricted to which roles. 
  </p>
        
  </section>
  
  <section name="Conclusion">
        
  <p>
  Interceptors, and interceptor factories, are a powerful concept that allow you to 
add consistent, efficient,
  robust behavior to your services. It takes a little while to wrap your brain around 
the idea of
  classes writing the code for other classes ... but once you do, a whole world of 
advanced techniques
  opens up to you!
  </p>
  
  </section>
  
  
        </body>
  </document>
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to