It's easier to do it from the get-go, rather than (like Tapestry) trying to loop back and do it after the fact.
-- Howard M. Lewis Ship Creator, Tapestry: Java Web Components http://jakarta.apache.org/tapestry http://jakarta.apache.org/commons/sandbox/hivemind/ http://javatapestry.blogspot.com > -----Original Message----- > From: Harish Krishnaswamy [mailto:[EMAIL PROTECTED] > Sent: Thursday, September 11, 2003 5:19 PM > To: Jakarta Commons Developers List > Subject: Re: cvs commit: > jakarta-commons-sandbox/hivemind/xdocs interceptors.xml navigation.xml > > > Got to tell you again, the amount of documentation you provide is > amazing! Bravo! > > -Harish > > [EMAIL PROTECTED] wrote: > > >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/ServiceInterceptorFa > ctory.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">Inter > > ceptorStack</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 ¶meters-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/Abstrac > tServiceInterceptorFactory.html">AbstractServiceInterceptorFac > tory</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.AbstractServiceInterc > eptorFactory; > > > > 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">Class > > Fab</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/Abstrac > tLoggingInterceptor.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().getExtensio nPointId()); > > > > 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.h > > tml">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] > > > > > > > > > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: [EMAIL PROTECTED] > For additional commands, e-mail: [EMAIL PROTECTED] > --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
