excalibur-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "hammett" <hamm...@uol.com.br>
Subject Excalibur Fortress now supports Interceptors (among other new features)
Date Sat, 10 Jul 2004 15:38:43 GMT
For a better read
http://jroller.com/comments/hammett?anchor=excalibur_fortress_now_supports_interceptors

Please consider the work that has been done, I'll issue a vote soon (before
enter the plane) ;-)


After a lof of sweat I've finished the interceptor support for Excalibur
Fortress. Its been a long road to achieve it, so I'll explain every step.

Events
------

Inspired by Berin's work on Dojo with events being a nice way to extend the
container, the first step was to modify the Container interface to allow
hooks:

EventManager getEventManager();

The EventManager obviously keeps the registered listeners and fire events.


Metainfo
--------

Metainfo could be a nice way to declare some special behavior of your class
(service implementation) and its methods. Fortress already use metainfo at
some extend. What I've done was to increase its support without changing the
current behavior. So now, during the container start up, the container
initialize what I've called ExtendedMetaInfo. This interface exposes all
attributes related to the class and its methods.

At any time your component can access the ExtendedMetaInfo if it looks up
for MetaInfoManager role.

Metainfo Collector
-----------------

The collect-meta task now loads and persists the class and methods
attributes. We still using qDox to parse the attributes, and now we're using
MetaClass project to handle serialization and deserialization of these tags.

InterceptorEnabledContainer
----------------------------

To avoid the kitchen sink container syndrome, the
InterceptorEnabledContainer was created. If you want to use interceptors,
you should create this container instead of DefaultContainer - or extend
InterceptorEnabledContainer instead of DefaultContainer.

What you need to know about InterceptorEnabledContainer is:
- It turn off the proxy manager. No component will be proxy but the
interceptable components.
- It searchs for an entry in the configuration file called
interceptorManager.
- It returns Context objects with a reference to Container and
MetaInfoManager using the keys "container" and "metamanager"

Besides that it instantiantes an implementation of InterceptorManager :-)

The InterceptorManager interface
---------------------------------

This interface was created to allow dinamic registration/creation of
interceptor. Usually you will rely on the configuration file to configure
your interceptors, but you can also do it after the container has already
started (but it would make more sense if you set lazy activation for the
components you wish to intercept ;-)

public interface InterceptorManager
{
    void add( String family, String name, String interceptorClass ) throws
InterceptorManagerException;

    void remove( String family, String name );

    Interceptor buildChain( String family ) throws IllegalAccessException,
InstantiationException;

    String[] getFamilies();
}

Family issues?
--------------

'Whats that thing of family? It doesn't sound familiar!'. I agree ;-) The
problem is, we need to categorize components and apply the same set of
interceptors to them. That's is how I imagine you will end you using
interceptors. Bear with me: you have a hundred components. Probably you
don't want to apply a different set of components to each one, so you'll
factor them into smaller "families" and mark each component as being part of
one particular family.

/**
 * @avalon.component
 * @avalon.service type=PersistenceManager
 * @x-avalon.info name=persistenceManager
 * @x-avalon.lifestyle type=singleton
 * @excalibur.interceptable family="businessObject"
 *
 * @author <a href="mailto:dev@excalibur.apache.org">Excalibur Development
Team</a>
 */
public class DefaultPersistenceManager implements PersistenceManager
{
    /**
     * @transaction.required
     * @security.enabled roles="Admin,Director,Worker"
     */
    public void persist(Object data)
    {
        // Working, working, working
    }
    /**
     * @transaction.supported
     */
    public Object load()
    {
        return "Data";
    }
}

And the respective configuration node will be:

<interceptorManager>
    <set family="businessObject">
      <interceptor
        name="security"

class="org.apache.avalon.fortress....interceptors.SecurityInterceptor" />
      <interceptor
        name="transactional"

class="org.apache.avalon.fortress....interceptors.TransactionalInterceptor"
/>
    </set>
</interceptorManager>

Interceptor
-----------

Interceptors are connected making a chain. The interceptor implementation
can do whatever it wants before and after invoking the next interceptor. The
point is: the interceptor doesn't know its position on the chain, so it
mustn't invoke the method itself.

public interface Interceptor
{
    void init( Interceptor next );

    Interceptor getNext();

    Object intercept( Object instance, ExtendedMetaInfo meta, Method method,
Object[] args )
        throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException;
}

A interceptor implementation can also add another interceptor on the chain
on the fly. Keep in mind that a chain instance is created for every
component instance that is interceptable. The suggestion is: try to use the
singleton lifestyle with interceptable components.

The final interceptor being invoked is called the tail interceptor. It is
responsible for the real invocation. If the method shouldn't be invoked at
all, your interceptor implementation might decide to not proceed to the next
interceptor in the chain.

Sample interceptor implementation
---------------------------------

This is a very un-optimized interceptor implementation made to illustrate
its usage

/**
 * Sample security interceptor. Checks if the current user have
 * the necessary role to execute the method.
 * This is just a sample and for the sake of readability
 * it hasn't been optimized in any way.
 *
 * @author <a href="mailto:dev@excalibur.apache.org">Excalibur Development
Team</a>
 */
public class SecurityInterceptor extends AbstractInterceptor
{
    /**
     * Checks the required roles and the current user role.
     */
    public Object intercept(Object instance, ExtendedMetaInfo meta, Method
method, Object[] args)
        throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
    {
        AttributeInfo attribute = meta.getAttributeForMethod(
"security.enabled", method );

        if (attribute != null)
        {
            boolean canAccess = false;
            final String roles = (String) attribute.getProperties().get(
"roles" );

            // First lets see if the current user can access

            final String currentRole = WhoAmI.instance().getRole();

            StringTokenizer tokenizer = new StringTokenizer(roles, ",");
            while( tokenizer.hasMoreTokens() )
            {
                final String token = tokenizer.nextToken();

                if (token.equalsIgnoreCase( currentRole ))
                {
                    canAccess = true;
                }
            }

            if (!canAccess)
            {
                throw new SecurityException("You don't have ne necessary
roles to access this method.");
            }
        }

        // Allows the chain to proceed.

        return super.intercept(instance, meta, method, args);
    }
}

InterceptableFactory
-------------------

Trying to lower the expensiveness of creating/invoking a method using
reflection the InterceptableFactory was created. It allows you to provide
your implementation of InterceptableFactory, which should return a new
wrapped component instance where the method invocation go throught the
interceptor chain.
Two implementation are available: The 'simple' uses simple reflection and
the other one uses CGLib's Enhancer. The performance tests I made resulted
in:

Reflection
testGetInterceptableComponent took 140 ms
testGetOrdinaryComponent took 78 ms

CGLIB
testGetInterceptableComponent took 125 ms
testGetOrdinaryComponent took 78 ms

CGLIB (Replacing tail)
testGetInterceptableComponent took 109 ms
testGetOrdinaryComponent took 78 ms

But what these tests don't show you is that CGLib approach takes more time
to return the component instance. So you need to measure well how many
interceptable components will be created and created and created during the
lifecycle of your application. If they are singletons, no problem as the
created only one time. Even if they are perthread it won't be a problem, I
guess.

Developers just want to have fun
--------------------------------

:-D While this was fun to develop, test, optimize it wasn't made on only on
my free time. We really need interceptor capabilities in our application to
interact with Transaction Manager (among other issues yet to be discussed).
As I happen to be a committer in Excalibur project it was another
opportunity to share this solution with other developers.

Well, if you want to dig into the code, go to
https://svn.apache.org/repos/asf/excalibur/branches/fortress-experiments/.


Cheers,
hammett


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@excalibur.apache.org
For additional commands, e-mail: dev-help@excalibur.apache.org
Apache Excalibur Project -- URL: http://excalibur.apache.org/


Mime
View raw message