You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@excalibur.apache.org by hammett <ha...@uol.com.br> on 2004/07/10 17:38:43 UTC

Excalibur Fortress now supports Interceptors (among other new features)

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/


Re: Excalibur Fortress now supports Interceptors (among other new features)

Posted by Peter Donald <pe...@realityforge.org>.
Leo Simons wrote:
> I can't help but think that we can move away (but support of course) 
> from our own xml format and use the metaclass one in one swell swoop. 

Thats what we did in Loom and it works fine as long as you 
have something that will read in an xml document and convert 
it into a set of MetaCLass attributes.

> Again, I think I see oppurtunity for bigger changes! We could get 
> completely rid of our old proxy management code and recast it as 
> utilization of some AOP or interceptor toolkit. That would completely 
> integrate interceptor support as to "tacking it on". WDYT?

+1

> How about this...we start another branch (so you don't need to miss your 
> project deadline) that's a little more radical and weaves dynaop and 
> metaclass right into the fortress core, then after that we look back and 
> try and figure out a way forward. WDYT?

+1

---------------------------------------------------------------------
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/


Re: Excalibur Fortress now supports Interceptors (among other new features)

Posted by Leo Simons <ls...@jicarilla.org>.
Other interceptor implementations
---------------------------------
Some direct links for comparison:

http://cvs.apache.org/viewcvs.cgi/jakarta-hivemind/framework/src/java/org/apache/hivemind/
http://cvs.sourceforge.net/viewcvs.py/springframework/spring/src/org/springframework/aop/
http://cvs.sourceforge.net/viewcvs.py/aopalliance/aopalliance/src/main/org/aopalliance/
https://dynaop.dev.java.net/source/browse/dynaop/src/dynaop/
http://cvs.codehaus.org/viewcvs.cgi/nanning/src/main/org/codehaus/nanning/?root=nanning

make sure to look at the Interceptor or MethodInterceptor class in all 
of these, how they are implemented in the examples, and how interceptors 
are matched to methods/components.

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

I've read through the code once. No attempt at compiling or using or 
understanding it fully just yet ;)

Event
-----
> EventManager getEventManager();

In general in cases like this I would rather see this in a seperate 
interface. Introducing a new method on a public interface is a bit 
tough, and here we can avoid it. ie

interface EventManagerProvider
{
   EventManager getEventManager();
}

class InterceptorContainer extends DefaultContainer
   implements EventManagerProvider { ... }

that said, we've got this event framework (d-haven event) we're using in 
other parts of the code. I think it would be good to use it consistently 
for events. Is that possible here? Mixing swing-like events with 
OO-style events seems like a recipe for confusion...

> Metainfo
> --------
I've fallen in love with this general principle that its good to write 
things in pure java and then when you introduce things like doc tags or 
xml files this just delegates to the java stuff. Fortress doesn't do 
this right now, but I'd definately like to introduce it at some point. 
Let's try and make sure that remains possible :-D

> 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.

Do you think it would be possible to just reuse the MetaClass Attribute 
class and access methods? For example 
DefaultInterceptorManager.obtainComponentFamily() could just use

   Attributes.getAttributes( method );

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

why? Isn't the metainfo supposed to be directed at the container, and 
the component is configured using Configurable semantics?

> 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.

QDoxSerializer leads the way!

I think step two is already done elsewhere:

http://svn.metaclass.codehaus.org/trunk/tools/src/java/org/codehaus/metaclass/tools/qdox/

I can't help but think that we can move away (but support of course) 
from our own xml format and use the metaclass one in one swell swoop. 
But that's bigger changes than what you're proposing.

Oh, and static singleton factories I no like :-D

> 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.

Again, I think I see oppurtunity for bigger changes! We could get 
completely rid of our old proxy management code and recast it as 
utilization of some AOP or interceptor toolkit. That would completely 
integrate interceptor support as to "tacking it on". WDYT?

> The InterceptorManager interface
> ---------------------------------
> Family issues?
> --------------

the way I'm seeing this is that "family" defines the association of any 
particular interceptor stack with any particular method on any 
particular component, ie it is a limited version of an "aspect". And 
instead of using pointcuts (ie 
https://dynaop.dev.java.net/nonav/release/1.0-beta/manual/ch01.html#d0e65) 
or some kind of interception point syntax (ie 
http://cvs.apache.org/viewcvs.cgi/jakarta-hivemind/framework/src/java/org/apache/hivemind/methodmatch/), 
there's this new concept.

I think its a better idea to go with the flow and terminology of the 
"pointcut" idea.

> Interceptor
> -----------
> Sample interceptor implementation
> ---------------------------------
> InterceptableFactory
> -------------------

looking pretty familiar again (= good thing). In my experience in the 
end I found that passing around an Invocation object is quite a bit 
cleaner, ie like

https://dynaop.dev.java.net/nonav/release/1.0-beta/manual/ch02s02.html#d0e317

> Trying to lower the expensiveness of creating/invoking a method using
> reflection the InterceptableFactory was created.

No offense, but I don't think we're able to maintain a very efficient 
proxy/interception library by ourselves. Looking at projects like 
HiveMind and Spring, you can also see how this has impacted them. A 
dedicated project like dynaop with dedicated experts is just so much faster.

(...)

How about this...we start another branch (so you don't need to miss your 
project deadline) that's a little more radical and weaves dynaop and 
metaclass right into the fortress core, then after that we look back and 
try and figure out a way forward. WDYT?

- LSD

---------------------------------------------------------------------
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/