Stomp

Plugging in method-level services

 

The main goal of the services framework in Stomp was to provide transaction demarcation to persistent objects.  It didn’t take long to realize that this generic mechanism could be used for other method-level services as well.  Benchmarking methods to find performance bottlenecks is one obvious application, and comes prepackaged with Stomp.  There are several ways to alter the set of services which are invoked during method calls on your enhanced objects.

 

The most common way to add a service is to alter the services.xml file for the given object.  For example, to add benchmarking to an enhanced object ‘Foo’, add a <services> element, with values of all the services you wish to have provided, ie.

 

<services>

            <Foo>

                        <services>

                                    <value>persistence</value>

                                    <value>benchmark</value>

                        <services>

            </Foo>

</services>

 

You don’t even have to recompile your objects for the changes to take effect.  If no services element is provided, Stomp assumes that persistence is the only service requested.  Note that if you add a services element in your metadata, you now need to explicitly add the persistence service (assuming you want this feature… you could also use Stomp to temporarily enhance a non-persistent object to add benchmarking for development purposes).

 

At runtime, Stomp adds the given services by instantiating service layers defined in your runtime prefs under the pref “serviceFactory.services”.  Your runtime.prefs file might look like this:

 

<prefs>

            <serviceFactory>

                        <services>

                                    <persistence>stomp.service.tx.TransactionServiceLayer</persistence>

                                    <benchmark>stomp.service.bench.BenchServiceLayer</benchmark>

                        </services>

            </serviceFactory>

</prefs>

 

At runtime, Stomp will match up the ‘benchmark’ service in services.xml with the stomp.service.bench.BenchServiceLayer and proceed accordingly.  Note that although the transaction demarcation and benchmarking services come with Stomp, there is nothing special about them.  It’s easy to write your own method level services, or extend the existing services, and plug them in exactly as shown above.  Stomp will use reflection at runtime to call an assumed ‘newInstance’ method in your service layer, passing in the ServiceEnabled object to wrap, expecting to receive a wrapped version in return.  It’s assumed that you will use a dynamic proxy to implement this method, using an InvocationHandler to intercept the methods you want.  Check out the source code for the BenchServiceLayer to get an idea of what you need to do to write your own service.

 

As a quick example, let’s say you just wanted to add a service which printed out a warning when a method returned a null object.  You’d write your own service which might look like this:

 

package com.myPackage;

 

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

import java.lang.reflect.Method;

 

import java.io.Serializable;

 

import stomp.util.Bench;

import stomp.util.Log;

 

/**

 * Prints out a message when methods are entered and exited.

*/

public class NullPointerChecker implements InvocationHandler, Serializable {

            private ServiceEnabled object;

 

            public NullPointerChecker ( ServiceEnabled object ) {

                        this.object = object;

            }

 

            /**

             * This is where you actually intercept the method and add your service

             */

            public Object invoke ( Object proxy, Method m, Object[] args ) throws Throwable {

                        try {

                                    Object o = return m.invoke ( object, args );

                                    if ( o == null )

                                                System.out.println (“warning! Object ” + object + “ just returned null from method “ + m.getName ());

                                    return o;

                        } catch ( InvocationTargetException ex ) {

                                    throw ex.getTargetException ();

                        }

}

 

            public static ServiceEnabled newInstance ( ServiceEnabled object, Class[] interfaces ) {

                        return (ServiceEnabled) Proxy.newProxyInstance ( object.getClass ().getClassLoader (), ServiceFactory.getInterfaces (object),

                                    new BenchServiceLayer ( object ) );

            }

}

 

Then you’d plug-in your service by changing services.xml, like this:

 

<services>

            <Foo>

                        <services>

                                    <value>persistence</value>

                                    <value>benchmark</value>

                                    <value>nullChecker</value>

                        </services>

            </Foo>

</services>

 

You could add nullChecker to as many services.xml files as you needed.  You’d complete the picture by telling Stomp that NullPointerChecker is the service you mean when you say ‘nullChecker’ in the services.xml file, by changing your runtime.prefs to look like this:

 

<prefs>

            <serviceFactory>

                        <services>

                                    <persistence>stomp.service.tx.TransactionServiceLayer</persistence>

                                    <benchmark>stomp.service.bench.BenchServiceLayer</benchmark>

                                    <nullChecker>com.myPackage.NullPointerChecker</nullChecker>

                        </services>

            </serviceFactory>

</prefs>

 

 

That’s it.  You don’t even have to rebuild the enhanced class.  Just compile the new NullPointerChecker service and add it to the CLASSPATH.  Next time you start up your application this service will be used.