1 2 Previous Next 21 Replies Latest reply on Feb 18, 2004 6:56 AM by Adrian Brock

    Unification of invocation and interceptors

    Scott Stark Master

      As a start to the discussion of how to unify the invocation and interceptor layers used in the aop, jmx, remoting and server frameworks, here are the some base classes derived from the current aop layer. We need to come to a consenus on what the comment layer should look like. This layer should be in the jmx module.


      /** Pretty basic common interface
      */
      public interface Interceptor
      {
       public String getName();
       public Object invoke(Invocation invocation) throws Throwable;
      }
      


      /** An encapsulation of the response: (value or exception) + attributes
      */
      public class InvocationResponse
       implements java.io.Externalizable
      {
       // Constants -----------------------------------------------------
       static final long serialVersionUID = -718723094688127810L;
      
       // The Map of methods used by this Invocation
       protected HashMap contextInfo = null;
       protected Object response = null;
       protected Throwable responseThrowable;
      
       public HashMap getContextInfo()
       {
       return contextInfo;
       }
       public void setContextInfo(HashMap contextInfo)
       {
       this.contextInfo = contextInfo;
       }
      
      
       // Constructors --------------------------------------------------
       public InvocationResponse()
       {
       }
       public InvocationResponse(Object obj)
       {
       this.response = obj;
       }
      
       public Object getResponse()
       {
       return response;
       }
       public void setResponse(Object obj)
       {
       this.response = obj;
       }
      
       public Throwable getResponseThrowable
       {
       return responseThrowable;
       }
       public void setResponseThrowable(Throwable cause)
       {
       this.responseThrowable = cause;
       }
      
       public void addAttachment(Object key, Object val)
       {
       if (contextInfo == null) contextInfo = new HashMap(1);
       contextInfo.put(key, val);
       }
      
       public Object getAttachment(Object key)
       {
       if (contextInfo == null) return null;
       return contextInfo.get(key);
       }
      
       // Externalizable implementation ---------------------------------
       public void writeExternal(java.io.ObjectOutput out)
       throws IOException
       {
       ...
       }
      
       public void readExternal(java.io.ObjectInput in)
       throws IOException, ClassNotFoundException
       {
       ...
       }
      }
      


      /** This class varies significantly... Here is a variation from
      the aop layer.
      */
      public class Invocation implements java.io.Serializable
      {
       protected InvocationType type;
       protected MetaData metadata = null;
       protected transient int currentInterceptor = 0;
       protected transient Interceptor[] interceptors = null;
       protected transient Object targetObject = null;
       protected transient InvocationResponse response;
      
       public Invocation(InvocationType type, Interceptor[] interceptors)
       {
       this(type, interceptors, null);
       }
      
       public Invocation(InvocationType type, Interceptor[] interceptors, MetaData meta)
       {
       this.interceptors = interceptors;
       this.type = type;
       this.metadata = meta;
       }
      
       public Invocation(Invocation invocation)
       {
       this.type = invocation.getType();
       this.interceptors = invocation.getInterceptors();
       targetObject = invocation.targetObject;
       }
      
       public final InvocationType getType()
       {
       return type;
       }
      
      
       public Object getTarget()
       {
       return targetObject;
       }
       public void setTarget(Object target)
       {
       this.targetObject = target;
       }
      
       public final InvocationResponse getResponse()
       {
       if( response == null )
       response = new InvocationResponse();
       return response;
       }
      
      
       public final void addResponseAttachment(Object key, Object val)
       {
       getResponse().addAttachment(key, val);
       }
       public final Object getResponseAttachment(Object key)
       {
       Object val = getResponse().getAttachment(key);
       return val;
       }
      
      
       /**
       * Return all the contextual data attached to this invocation
       */
       public final MetaData getMetaData()
       {
       if (metadata == null) metadata = new SimpleMetaData();
       return metadata;
       }
       /**
       * Set all the contextual data attached to this invocation
       */
       public final void setMetaData(MetaData data)
       {
       this.metadata = data;
       }
      
       /**
       * Invoke on the next interceptor in the chain. If this is already
       * the end of the chain, reflection will call the constructor, field, or
       * method you are invoking on.
       */
       public final Object invokeNext() throws Throwable
       {
       try
       {
       return interceptors[currentInterceptor++].invoke(this);
       }
       finally
       {
       // so that interceptors like clustering can reinvoke down the chain
       currentInterceptor--;
       }
       }
      
       /** Is this useful?
       *
       */
       public final Object invokeNext(org.jboss.aop.advice.Interceptor[] newInterceptors) throws Throwable
       {
       // Save the old stack position
       org.jboss.aop.advice.Interceptor[] oldInterceptors = interceptors;
       int oldCurrentInterceptor = currentInterceptor;
      
       // Start the new stack
       interceptors = newInterceptors;
       currentInterceptor = 0;
      
       // Invoke the new stack
       try
       {
       return invokeNext();
       }
       finally
       {
       // Restore the old stack
       interceptors = oldInterceptors;
       currentInterceptor = oldCurrentInterceptor;
       }
       }
      
       public final Interceptor[] getInterceptors()
       {
       return interceptors;
       }
      
      
       public final void setInterceptors(Interceptor[] interceptors)
       {
       this.interceptors = interceptors;
       }
      
      
       /**
       * This method resolves metadata based on the context of the invocation.
       * It iterates through its list of MetaDataResolvers to find out the
       * value of the metadata desired. Can't these be embedded in the metadata?
       *
       */
       public Object getMetaData(Object group, Object attr)
       {
       Object val = null;
       if (this.metadata != null)
       {
       val = this.metadata.resolve(this, group, attr);
       if (val != null) return val;
       }
      
       return null;
       }
      }
      


      /** Why should MetaData know how to resolve which meta data is needed for a
       a given invocation?
       */
      public class SimpleMetaData implements MetaDataResolver, java.io.Serializable
      {
       protected HashMap metaData = new HashMap();
      
       public static class MetaDataValue implements java.io.Serializable
       {
      
       public final PayloadKey type; // One of "TRANSIENT", "AS_IS", "MARSHALLED"
       public final Object value;
      
       public MetaDataValue(PayloadKey type, Object value)
       {
       this.type = type;
       this.value = value;
       }
      
       public Object get()
       throws IOException, ClassNotFoundException
       {
       if (value instanceof MarshalledValue)
       {
       return ((MarshalledValue)value).get();
       }
       return value;
       }
      
       }
      
       public synchronized HashSet groups()
       {
       return new HashSet(metaData.keySet());
       }
      
       public synchronized HashMap group(String name)
       {
       HashMap map = (HashMap)metaData.get(name);
       if (map == null) return null;
       return (HashMap)map.clone();
       }
      
       public synchronized boolean hasGroup(String name)
       {
       return metaData.get(name) != null;
       }
      
      
       public void addMetaData(Object group, Object attr, Object value)
       {
       addMetaData(group, attr, value, PayloadKey.MARSHALLED);
       }
      
       public synchronized void addMetaData(Object group, Object attr, Object value, PayloadKey type)
       {
       HashMap groupData = (HashMap)metaData.get(group);
       if (groupData == null)
       {
       groupData = new HashMap();
       metaData.put(group, groupData);
       }
       MetaDataValue val = new MetaDataValue(type, value);
       groupData.put(attr, val);
       }
      
       public synchronized Object getMetaData(Object group, Object attr)
       {
       try
       {
       HashMap groupData = (HashMap)metaData.get(group);
       if (groupData == null) return null;
       MetaDataValue val = (MetaDataValue)groupData.get(attr);
       if (val == null) return null;
       return val.get();
       }
       catch (IOException ioex)
       {
       throw new RuntimeException("failed on MarshalledValue", ioex);
       }
       catch (ClassNotFoundException ex)
       {
       throw new RuntimeException("failed on MarshalledValue", ex);
       }
       }
      
       public synchronized void removeMetaData(Object group, Object attr)
       {
       HashMap groupData = (HashMap)metaData.get(group);
       if (groupData != null)
       {
       groupData.remove(attr);
       }
       }
      
       public synchronized void removeGroupData(Object group)
       {
       metaData.remove(group);
       }
      
       public synchronized void clear()
       {
       metaData.clear();
       }
      
       /**
       * merges incoming data. Incoming data overrides existing data
       */
       public synchronized void mergeIn(SimpleMetaData data)
       {
       Iterator it = data.metaData.keySet().iterator();
       while (it.hasNext())
       {
       Object group = it.next();
       HashMap attrs = (HashMap)data.metaData.get(group);
       HashMap map = (HashMap)metaData.get(group);
       if (map == null)
       {
       map = new HashMap();
       this.metaData.put(group, map);
       }
       map.putAll(attrs);
       }
       }
      
       public synchronized Object resolve(Invocation invocation, Object group, Object attr)
       {
       return getMetaData(group, attr);
       }
      
       public SimpleMetaData getAllMetaData(Invocation invocation)
       {
       return this;
       }
      
       public void writeExternal(java.io.ObjectOutput out)
       throws IOException
       {
       ...
       }
      
       public void readExternal(java.io.ObjectInput in)
       throws IOException, ClassNotFoundException
       {
       ...
       }
      
      }
      


      public abstract class Dispatcher
      {
       HashMap targetMap = new HashMap();
      
      
       public boolean isRegistered(Object oid)
       {
       synchronized (targetMap)
       {
       return targetMap.containsKey(oid);
       }
       }
       /**
       * Register an Object ID with an actual target object
       */
       public void registerTarget(Object oid, Object target)
       {
       synchronized (targetMap)
       {
       targetMap.put(oid, target);
       }
       }
      
       public void unregisterTarget(Object oid)
       {
       synchronized (targetMap)
       {
       targetMap.remove(oid);
       }
       }
      
       public Object getRegistered(Object oid)
       {
       synchronized (targetMap)
       {
       return targetMap.get(oid);
       }
       }
      
       public InvocationResponse invoke(Invocation invocation) throws Throwable
       {
       Invocation invocation = processInvocation();
       InvocationReponse response;
       try
       {
       Object target = findTarget(invocation);
       invocation.setTarget(target);
       Object result = dispatch(invocation);
       response = invocation.getOrCreateResponse();
       response.setResult(result);
       }
       catch (Throwable t)
       {
       response = invocation.getOrCreateResponse();
       response.setThrowable(t)
       }
       return response;
       }
      
       public abstract Invocation processInvocation();
       public abstract Object findTarget(Invocation inv, Dispatcher d);
       public abstract Object dispatch(Invocation inv) throws Throwable;
      }
      


        • 1. Re: Unification of invocation and interceptors
          Juha Lindfors Master

          Do we need a separate InvocationResponse? All of those fields could be just as easily be included inside the original Invocation instance as well, could they not?

          • 2. Re: Unification of invocation and interceptors
            Juha Lindfors Master

            nevermind, InvocationResponse is already a field inside Invocation, ignore

            • 3. Re: Unification of invocation and interceptors
              Juha Lindfors Master

              invokeNext(newInterceptors) I think is much too restrictive in its current form. Why not just have access to the interceptor array directly, so the stack can be modified at any point, the new interceptors don't have to be invoked right away.

              How do I add a new interceptor for the return for example? If at the top of the stack, I want to add a "return interceptor behind me".

              • 4. Re: Unification of invocation and interceptors
                Bill Burke Master

                YOu can't add an interceptor for the return as it is one call. We don't want to add ability (adding a return interceptor) as it break the simplicity of try/catch blocks and invocation flow.

                Also. InvocationResponse is really only useful when going across network boundaries as there needs to be a way to piggyback metadata as well as propagating the return value or exception.

                • 5. Re: Unification of invocation and interceptors
                  Bill Burke Master

                  Another thing about the Invocation object in the AOP layer is the concept of metadata resolving. Metadata is resolved from the context of the invocation. This is important because to reuse something like the security interceptor, how it obtains method permissions must be isolated as the definition will be different in JMX, AOP, and EJB land.

                  Furthermore, security is an interesting problem as you don't want to be able to override method permissions from the Invocation's metadata. So we need a method like:

                  resolveConcreteMetaData(group, attribute);

                  So that security can ignore thread and invocation metadata which can be overridden by the user, and instead resolve it from a "secure" place.

                  Bill

                  • 6. Re: Unification of invocation and interceptors
                    Juha Lindfors Master

                    Try - finally block is just a convenient programming trick, is not bringing any new functionality and if its restricting design is not worth keeping as such, it takes just the same time to write if - else based on invocations metadata which reveals whether its on IN or OUT path.

                    • 7. Re: Unification of invocation and interceptors
                      marc fleury Master

                      on the topic of return interceptors.

                      Try catch is ok but the important point is interceptor variable visibility across in and out. For example think TX interceptor, the behavior of the interceptor on the OUT part depends on the IN part (was there a transation in a REquires NEW mode?). If you don't have one instance dealing with that, you are stuck with stuffing that information in the invocation object and retrieving it on the way out.

                      IN/OUT separation of interceptors make it just more complex to write for not functional advantage (that I can see)

                      • 8. Re: Unification of invocation and interceptors
                        Scott Stark Master

                        I also do not see why the Invocation should be directing any of the interceptor usage, which is really a dispatch semantic. The use of the Invocation.invokeNext(...) is optional since the initiation point for the invocation can be the Dispatcher.

                        Two different dispatch mechanisms have been advocated.
                        - Start with the first interceptor and pass the invocation down the chain until the last interceptor, which has the responsibility of dispatching the invocation to the target. This is what we do currently with either an explicit last dispatch interceptor or a non-interceptor dispatcher.
                        - Treat each interceptor as a worker that simply modifies the state of the invocation and has no explicit call stack semantic. There could be seperate interceptor assemblies for the invocation dispatch and result.

                        The current call dispatch notion may appear simple and well defined, but it breaks down when you start talking about asynch requests, requests traversing thread pools, routing of requests based on quality of service choices, etc.

                        • 9. Re: Unification of invocation and interceptors
                          Scott Stark Master

                           

                          Also. InvocationResponse is really only useful when going across network boundaries as there needs to be a way to piggyback metadata as well as propagating the return value or exception.


                          This is not true. Moving a request across thread pools and service layers also requires that you do not maintain the strict direct call stack semantic. Routing choices and exception handling can be affected by metadata associated with the response. Also, having to choose how the return value/exception are propagated depending on whether or not there is a transport layer is a complexity that does not need to show up if there is an InvocationResponse.

                          • 10. Re: Unification of invocation and interceptors
                            marc fleury Master

                            Letting a "manager" control the stack, rather than a self assembled list doesn't sound like a bad idea.

                            I have seen this pattern in the past. It has the advantage of letting us control the stack through that manager.

                            • 11. Re: Unification of invocation and interceptors
                              Bill Burke Master

                               

                              "marc fleury" wrote:
                              on the topic of return interceptors.

                              Try catch is ok but the important point is interceptor variable visibility across in and out. For example think TX interceptor, the behavior of the interceptor on the OUT part depends on the IN part (was there a transation in a REquires NEW mode?). If you don't have one instance dealing with that, you are stuck with stuffing that information in the invocation object and retrieving it on the way out.

                              IN/OUT separation of interceptors make it just more complex to write for not functional advantage (that I can see)


                              I agree with Marc. 99% of use cases wil not want to chance the interceptor stack. try/catch simplifies the programming model.

                              Bill

                              • 12. Re: Unification of invocation and interceptors
                                Bill Burke Master

                                 

                                "marc fleury" wrote:
                                Letting a "manager" control the stack, rather than a self assembled list doesn't sound like a bad idea.

                                I have seen this pattern in the past. It has the advantage of letting us control the stack through that manager.



                                In JBoss AOP a "manager" does control the stack and injects it into the Invocation object. Then the individual interceptors have the option to change the stack. You don't want to have the ability to change the interceptor array directly as the "manager" shouldn't should be able to avoid allocating a new copy of the advice stack. When you get down to the pojo level this is extremely important.

                                Bill

                                • 13. Re: Unification of invocation and interceptors
                                  Juha Lindfors Master

                                   

                                  "Bill Burke" wrote:

                                  I agree with Marc. 99% of use cases wil not want to chance the interceptor stack. try/catch simplifies the programming model.


                                  Try - finally simplicity is not a point (if - else achieves the same). Variable visibility for a stateful interceptor however is a good point.

                                  So we come back to the discussion of stateless vs. stateful interceptors. If state is needed, should it belong to the interceptor or to the invocation?

                                  Should all interceptors be stateless and can be shared across POJO's (scalability). Or do we build stateful interceptors -- interceptor instance per POJO (very high number of instances even on a small interceptor stack if a lot of POJO's are instrumented).

                                  What you're arguing for (or marc) is an interceptor instance per POJO (when stateful). However, intuition says keep everything stateless as much as possible (that is, let state live within an invocation -- like HTTP -- but the server should remain stateless).

                                  Should there be a distinction in the interfaces between stateless and stateful interceptors (for instance, the Interceptor and SharedInterceptors that exist in the JMX base)? Do we always require a stack of interceptor instance per POJO?




                                  • 14. Re: Unification of invocation and interceptors
                                    Juha Lindfors Master

                                    Thinking about it a little further, even the TX interceptor case state is only meaningful for the lifetime of that invocation. Keeping the state around in memory of the server when there is no ongoing invocation seems a little wasteful.

                                    Consider for instance a POJO I've instrumented as transactional. This POJO gets invoked once a week. Why should I keep the state of that transactional interceptor in memory on my server for a whole week if I only need it for few milliseconds once a week. For such a case it would make sense (from resource usage point of view) to have a stateless TX interceptor and hold that state in memory for the few milliseconds it takes to invoke my POJO.

                                    With EJB we did not have this issue as the interceptor stack was per container, not per object identity.

                                    Stateful implementations are usually easier to implement than stateless though. However, people have managed to build easy-to-use APIs on top of stateless HTTP invocations (Servlet API) that hides the complexity from the developer so why could we not do the same? Is implementing servlet Filters difficult, do we need a different model from that?


                                    1 2 Previous Next