10 Replies Latest reply on Oct 6, 2005 3:40 PM by Adrian Brock

    "Proxy" advisors

    Adrian Brock Master

      I had a look at what it would take to write a "Proxy" advisor
      similar to what is done for the current proxy containers in org.jboss.proxy

      I came up with a simple (and incomplete) version that is based on the ClassContainer.

      One of the issues I came across was you cannot add per instance advices unless
      the target has an instance advisor, which is something that I could easily work around
      by allowing the advisor to be an instance advisor as well with the following code change
      in the PerInstanceAdvice/Interceptor

       if (targetObject == null) return invocation.invokeNext(); // static method call or static field call
      
       InstanceAdvisor instanceAdvisor = null;
      + if (targetObject instanceof Advised)
      + {
       Advised advised = (Advised) targetObject;
       instanceAdvisor = advised._getInstanceAdvisor();
      + }
      + else
      + {
      + Advisor advisor = invocation.getAdvisor();
      + if (advisor == null || advisor instanceof InstanceAdvisor == false)
      + return invocation.invokeNext();
      
      + instanceAdvisor = (InstanceAdvisor) advisor;
      + }
      
      


      Here is the prototype. Most of it is copied from the instance advisor code,
      some of it overrides the Advisor code to allow the proxy to implement
      interfaces that are neither introduced or mixed in on the target class/object.

      /*
       * JBoss, the OpenSource J2EE webOS
       *
       * Distributable under LGPL license.
       * See terms of license at gnu.org.
       */
      package org.jboss.aop.container;
      
      import java.lang.reflect.Constructor;
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      import java.util.Iterator;
      import java.util.Map;
      import java.util.Set;
      import java.util.WeakHashMap;
      
      import org.jboss.aop.AspectManager;
      import org.jboss.aop.ClassContainer;
      import org.jboss.aop.Domain;
      import org.jboss.aop.InstanceAdvisor;
      import org.jboss.aop.MethodJoinPoint;
      import org.jboss.aop.advice.AspectDefinition;
      import org.jboss.aop.advice.Interceptor;
      import org.jboss.aop.joinpoint.Joinpoint;
      import org.jboss.aop.joinpoint.MethodInvocation;
      import org.jboss.aop.metadata.SimpleMetaData;
      import org.jboss.aop.util.MethodHashing;
      
      import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
      
      /**
       * A ProxyAdvisor.
       *
       * @author <a href="adrian@jboss.com">Adrian Brock</a>
       * @version $Revision: 1.1 $
       */
      public class ProxyAdvisor extends ClassContainer implements InstanceAdvisor
      {
       /** The interfaces */
       protected Class[] interfaces;
      
       /** The target */
       protected Object target;
      
       /** All methods */
       protected Method[] allMethods;
      
       /** The instance aspects */
       protected transient WeakHashMap instanceAspects;
      
       /** The joinpoint aspects */
       protected transient WeakHashMap joinpointAspects;
      
       /** The invocatin handler */
       protected InvocationHandler handler = new ProxyAdvisorInvocationHandler();
      
       /**
       * Create an advised proxy
       *
       * @param target the target
       * @return the advised proxy
       */
       public static Object createAdvisedProxy(Object target)
       {
       if (target == null)
       throw new IllegalArgumentException("No target");
       return createAdvisedProxy(target.getClass().getClassLoader(), target);
       }
      
       /**
       * Create an advised proxy
       *
       * @param cl the classloader
       * @param target the target
       * @return the advised proxy
       */
       public static Object createAdvisedProxy(ClassLoader cl, Object target)
       {
       if (target == null)
       throw new IllegalArgumentException("No target");
       return createAdvisedProxy(cl, target.getClass().getInterfaces(), target);
       }
      
       /**
       * Create an advised proxy
       *
       * @param cl the classloader
       * @param interfaces the interfaces
       * @param target the target
       * @return the advised proxy
       */
       public static Object createAdvisedProxy(ClassLoader cl, Class[] interfaces, Object target)
       {
       AspectManager manager = AspectManager.instance();
       return createAdvisedProxy(cl, interfaces, target, manager);
       }
      
       /**
       * Create an advised proxy
       *
       * @param cl the classloader
       * @param interfaces the interfaces
       * @param target the target
       * @param manager the aspect manager
       * @return the advised proxy
       */
       public static Object createAdvisedProxy(ClassLoader cl, Class[] interfaces, Object target, AspectManager manager)
       {
       if (target == null)
       throw new IllegalArgumentException("Null target");
       ProxyAdvisor advisor = new ProxyAdvisor(manager, interfaces, target);
       return Proxy.newProxyInstance(cl, interfaces, advisor.handler);
       }
      
       /**
       * Create a new ProxyAdvisor.
       *
       * @param manager the aspect manager
       * @param interfaces the interfaces
       * @param target the target
       */
       protected ProxyAdvisor(AspectManager manager, Class[] interfaces, Object target)
       {
       super("ProxyAdvisor", manager);
       this.interfaces = interfaces;
       this.target = target;
       initializeClassContainer();
       initializeInstanceAspects();
       initializeJoinpointAspects();
       }
      
       public Method[] getAllMethods()
       {
       return allMethods;
       }
      
       protected void createMethodMap()
       {
       try
       {
       if (interfaces != null)
       {
       for (int i = 0; i < interfaces.length; ++i)
       {
       Class intf = interfaces;
       Method[] declaredMethods = intf.getMethods();
       for (int j = 0; j < declaredMethods.length; j++)
       {
       long hash = MethodHashing.methodHash(declaredMethods[j]);
       advisedMethods.put(hash, declaredMethods[j]);
       }
       }
       Object[] values = advisedMethods.getValues();
       allMethods = new Method[values.length];
       System.arraycopy(values, 0, allMethods, 0, values.length);
       }
       }
       catch (Exception e)
       {
       throw new RuntimeException("Unexpected error", e);
       }
       }
      
       protected void createConstructorTables()
       {
       constructors = new Constructor[0];
       }
      
       public void appendInterceptor(int index, Interceptor interceptor)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI appendInterceptor");
       }
      
       public void appendInterceptor(Interceptor interceptor)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI appendInterceptor");
       }
      
       public void appendInterceptorStack(String stackName)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI appendInterceptorStack");
       }
      
       public Domain getDomain()
       {
       throw new RuntimeException("getDomain() is only available when you use weaving with generated advisors");
       }
      
       public Interceptor[] getInterceptors()
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI getInterceptors");
       }
      
       public Interceptor[] getInterceptors(Interceptor[] baseChain)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI getInterceptors");
       }
      
       public SimpleMetaData getMetaData()
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI getMetaData");
       }
      
       public Object getPerInstanceAspect(AspectDefinition def)
       {
       Object aspect = instanceAspects.get(def);
       if (aspect == null)
       {
       synchronized (this) // doublecheck, but I don't want to synchronize everywhere and dynamic aspects are rare
       {
       aspect = instanceAspects.get(def);
       if (aspect != null) return aspect;
       getPerInstanceAspectDefinitions().add(def);
       aspect = def.getFactory().createPerInstance(null, null);
       WeakHashMap copy = new WeakHashMap(instanceAspects);
       copy.put(def, aspect);
       instanceAspects = copy;
       }
       }
       return aspect;
       }
      
       public Object getPerInstanceAspect(String aspectName)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI getPerInstanceAspect");
       }
      
       public Object getPerInstanceJoinpointAspect(Joinpoint joinpoint, AspectDefinition def)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI getPerInstanceJoinpointAspect");
       }
      
       public boolean hasInterceptors()
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI hasInterceptors");
       }
      
       public void insertInterceptor(int index, Interceptor interceptor)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI insertInterceptor");
       }
      
       public void insertInterceptor(Interceptor interceptor)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI insertInterceptor");
       }
      
       public void insertInterceptorStack(String stackName)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI insertInterceptorStack");
       }
      
       public void removeInterceptor(String name)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI removeInterceptor");
       }
      
       public void removeInterceptorStack(String name)
       {
       throw new org.jboss.util.NotImplementedException("FIXME NYI removeInterceptorStack");
       }
      
       protected synchronized void initializeInstanceAspects()
       {
       Set defs = getPerInstanceAspectDefinitions();
       if (defs.size() > 0)
       {
       instanceAspects = new WeakHashMap();
       Iterator it = defs.iterator();
       while (it.hasNext())
       {
       AspectDefinition def = (AspectDefinition) it.next();
       Object aspect = def.getFactory().createPerInstance(this, this);
       instanceAspects.put(def, aspect);
       }
       }
      
       }
      
       private synchronized void initializeJoinpointAspects()
       {
       Map jpAspects = getPerInstanceJoinpointAspectDefinitions();
       if (jpAspects.size() > 0)
       {
       joinpointAspects = new WeakHashMap();
       Iterator it = jpAspects.keySet().iterator();
       while (it.hasNext())
       {
       AspectDefinition def = (AspectDefinition) it.next();
       ConcurrentReaderHashMap joins = new ConcurrentReaderHashMap();
       joinpointAspects.put(def, joins);
       Set joinpoints = (Set) jpAspects.get(def);
       Iterator jps = joinpoints.iterator();
       while (jps.hasNext())
       {
       Object joinpoint = jps.next();
       joins.put(joinpoint, def.getFactory().createPerJoinpoint(this, this, (Joinpoint) joinpoint));
       }
       }
       }
       }
      
       /**
       * A ProxyAdvisorInvocationHandler.
       */
       private class ProxyAdvisorInvocationHandler implements InvocationHandler
       {
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
       {
       long hash = MethodHashing.calculateHash(method);
       MethodJoinPoint joinPoint = (MethodJoinPoint) methodInterceptors.get(hash);
       Interceptor[] interceptors = null;
       if (joinPoint != null)
       interceptors = joinPoint.interceptors;
       else if (Object.class != method.getDeclaringClass())
       throw new IllegalArgumentException("Unknown method " + method);
       MethodInvocation mi = new MethodInvocation(interceptors, hash, method, method, null);
       mi.setTargetObject(target);
       mi.setArguments(args);
       mi.setAdvisor(ProxyAdvisor.this);
       return mi.invokeNext();
       }
       }
       }
      


      You can then use it like the following:
      MessageEndpointFactory implementation:
      
       public MessageEndpoint createEndpoint(XAResource resource) throws UnavailableException
       {
       if (target == null)
       throw new IllegalStateException("Null target");
       if (manager == null)
       throw new IllegalStateException("Null manager");
       if (messageListener == null)
       throw new IllegalStateException("Null messageListener");
      
       ClassLoader cl = Thread.currentThread().getContextClassLoader();
       Class[] interfaces = new Class[] { messageListener, MessageEndpoint.class };
       return (MessageEndpoint) ProxyAdvisor.createAdvisedProxy(cl, interfaces, target, manager);
       }
      


        • 1. Re:
          Bill Burke Master

          did you take a look under

          org.jboss.aop.proxy

          and

          org.jboss.aop.proxy.container?

          The stuff under proxy package that is a simple proxy that just has a set of interceptors that will intercept every method. It also supports mixins.

          the stuff under proxy.container is based on ClassContainer and allows you full AOP (except call/within of course). Basically it it a container for a delegating target object. If you look at the basic AOP/MC integration I did, you can do JBoss AOP with MC created beans without bytecode weaving.

          • 2. Re:
            Adrian Brock Master

             

            "bill.burke@jboss.com" wrote:
            did you take a look under

            org.jboss.aop.proxy

            and

            org.jboss.aop.proxy.container?

            The stuff under proxy package that is a simple proxy that just has a set of interceptors that will intercept every method. It also supports mixins.

            the stuff under proxy.container is based on ClassContainer and allows you full AOP (except call/within of course). Basically it it a container for a delegating target object. If you look at the basic AOP/MC integration I did, you can do JBoss AOP with MC created beans without bytecode weaving.


            Yes, but none of them meet my requirements, which was why I wrote something did.

            The org.jboss.aop.proxy adds a mixin that cannot be intercepted and constructs a
            proxy via javassist (the later isn't necessarily important when I've fixed it to work with a security manager).

            The proxy.container stuff requires the user to do too much work with joinpoints
            and generally understanding/constructing the "invocation context".

            What I want is:
            * A simple factory that can advise a POJO via a proxy (not necessarily a java.lang.reflect.Proxy)
            * Uses the object itself if it is already advised
            * The factory lets you pass a contextual reference to the aop config domain
            * It uses the aop config from that domain, i.e. it uses pointcuts/annotations
            * Still supports explicit "interceptor chain" construction like org.jboss.proxy (similar to what EJB3/JBoss Messaging)
            * Is extensible such that the "SimpleMetaData" can be enhanced with abitrary metadata
            (org.jboss.proxy only supports instance enhancements)
            * Provides a mechanism to override annotations on the advised POJO for this proxy instance

            I have a prototype on my laptop that uses this to do JCA inbound messaging to a POJO
            plus some other parts of the JCA spec in a POJO environment configured through
            the microcontainer (in very little code and a lot more extensible/overridable than
            the current jca1.5 implementation :-)

            I posted how it uses it before. Besides the reference to the AspectManager/domain
            it is completely unaware of the AOP implementation. It just uses it as a "Proxy Factory".
            Compare that to all the extra AOP plumbing that the JBoss Messaging team are doing...


            • 3. Re:
              Bill Burke Master

               

              "adrian@jboss.org" wrote:
              "bill.burke@jboss.com" wrote:
              did you take a look under

              org.jboss.aop.proxy

              and

              org.jboss.aop.proxy.container?

              The stuff under proxy package that is a simple proxy that just has a set of interceptors that will intercept every method. It also supports mixins.

              the stuff under proxy.container is based on ClassContainer and allows you full AOP (except call/within of course). Basically it it a container for a delegating target object. If you look at the basic AOP/MC integration I did, you can do JBoss AOP with MC created beans without bytecode weaving.


              Yes, but none of them meet my requirements, which was why I wrote something did.

              The org.jboss.aop.proxy adds a mixin that cannot be intercepted and constructs a
              proxy via javassist (the later isn't necessarily important when I've fixed it to work with a security manager).


              javassist proxy is both for speed.



              The proxy.container stuff requires the user to do too much work with joinpoints
              and generally understanding/constructing the "invocation context".


              So you want something like the old org.jboss.proxy stuff, yet you want it to work with all the annotation overriding and all the aspects these annotations might trigger.


              What I want is:
              * A simple factory that can advise a POJO via a proxy (not necessarily a java.lang.reflect.Proxy)
              * Uses the object itself if it is already advised


              We are deprecating the InstanceAdvisor interface so that you can do pointcuts on a per-instance basis.


              * The factory lets you pass a contextual reference to the aop config domain
              * It uses the aop config from that domain, i.e. it uses pointcuts/annotations


              This is exactly how the proxy.container stuff works. Look at ClassProxyConstructorJoinpoint


              * Still supports explicit "interceptor chain" construction like org.jboss.proxy (similar to what EJB3/JBoss Messaging)


              Again, proxy.container can support this and was made to support this idea for MC/AOP integration.


              * Is extensible such that the "SimpleMetaData" can be enhanced with abitrary metadata
              (org.jboss.proxy only supports instance enhancements)


              don't understand.


              * Provides a mechanism to override annotations on the advised POJO for this proxy instance


              Again, proxy.container was made for this. Please see the kernel/AOP integration tests.



              I have a prototype on my laptop that uses this to do JCA inbound messaging to a POJO
              plus some other parts of the JCA spec in a POJO environment configured through
              the microcontainer (in very little code and a lot more extensible/overridable than
              the current jca1.5 implementation :-)

              I posted how it uses it before. Besides the reference to the AspectManager/domain
              it is completely unaware of the AOP implementation. It just uses it as a "Proxy Factory".
              Compare that to all the extra AOP plumbing that the JBoss Messaging team are doing...


              please elaborate on what JBoss Messaging is doing wrong.

              • 4. Re:
                Adrian Brock Master

                 

                "bill.burke@jboss.com" wrote:

                please elaborate on what JBoss Messaging is doing wrong.


                It has its own "proxy" advisor heavily dependent on implementations of the AOP model
                see org.jboss.jms.server.endpoint.ServerConnectionDelegate and lots of related code.
                I see the same thing in EJB3.

                I don't want every part of JBoss writing its own proxy advisor/invocation handler
                dependent on AOP implementation details such that you can't change anything
                in AOP because you have to refactor everything else.

                This code should be done once with a "simple" interface/contract.

                Again, proxy.container can support this and was made to support this idea for MC/AOP integration.


                So how I would use it as a proxy factory?

                P.S. The last time we discussed anything was here:
                http://www.jboss.com/index.html?module=bb&op=viewtopic&t=63944
                I notice you've deleted that failing test.

                • 5. Re:
                  Adrian Brock Master

                   

                  "bill.burke@jboss.com" wrote:


                  * Is extensible such that the "SimpleMetaData" can be enhanced with abitrary metadata
                  (org.jboss.proxy only supports instance enhancements)



                  See the EJB2.1 MEF
                   public MessageEndpoint createEndpoint(XAResource resource) throws UnavailableException
                   {
                   trace = log.isTraceEnabled();
                  
                   if (getState() != STARTED && getState() != STARTING)
                   throw new UnavailableException("The container is not started");
                  
                   HashMap context = new HashMap();
                   context.put(MessageEndpointInterceptor.MESSAGE_ENDPOINT_FACTORY, this);
                   context.put(MessageEndpointInterceptor.MESSAGE_ENDPOINT_XARESOURCE, resource);
                  
                   String ejbName = container.getBeanMetaData().getContainerObjectNameJndiName();
                  
                   if (trace)
                   log.trace("createEndpoint " + this + " xaResource=" + resource);
                  
                   MessageEndpoint endpoint = (MessageEndpoint) proxyFactory.createProxy
                   (
                   ejbName + "@" + nextProxyId.increment(),
                   container.getServiceName(),
                   InvokerInterceptor.getLocal(),
                   null,
                   null,
                   interceptors,
                   container.getClassLoader(),
                   interfaces,
                   context
                   );
                  
                   if (trace)
                   log.trace("Created endpoint " + endpoint + " from " + this);
                  
                   return endpoint;
                   }
                  


                  The proxy factory allows instance level context to be added for later
                  use by the advice. In this case the MEF itself and the ResourceAdapter's
                  XAResource.

                  • 6. Re:
                    Bill Burke Master

                     

                    "adrian@jboss.org" wrote:
                    "bill.burke@jboss.com" wrote:

                    please elaborate on what JBoss Messaging is doing wrong.


                    It has its own "proxy" advisor heavily dependent on implementations of the AOP model
                    see org.jboss.jms.server.endpoint.ServerConnectionDelegate and lots of related code.
                    I see the same thing in EJB3.

                    I don't want every part of JBoss writing its own proxy advisor/invocation handler
                    dependent on AOP implementation details such that you can't change anything
                    in AOP because you have to refactor everything else.

                    This code should be done once with a "simple" interface/contract.

                    Again, proxy.container can support this and was made to support this idea for MC/AOP integration.


                    So how I would use it as a proxy factory?

                    P.S. The last time we discussed anything was here:
                    http://www.jboss.com/index.html?module=bb&op=viewtopic&t=63944
                    I notice you've deleted that failing test.


                    Well, you have to decide what you want. A common proxy/ih, or optimized invocations. I chose to optimize proxy serialization. This is especially important for local interfaces in EJB as the proxy needs to locate the EJB container after it is serialized so it can make a direct invocation on it.

                    • 7. Re:
                      Adrian Brock Master

                       

                      "bill.burke@jboss.com" wrote:

                      Well, you have to decide what you want. A common proxy/ih, or optimized invocations. I chose to optimize proxy serialization. This is especially important for local interfaces in EJB as the proxy needs to locate the EJB container after it is serialized so it can make a direct invocation on it.


                      You'll have to explain that to me. It sounds like a false dichotomy.

                      I want to create an advice stack that can be configured using pointcuts (in a domain).
                      I want to have to deal with as little as possible with AOP implementation details
                      or its internal deployment model (in my code that creates the proxy - not the advices
                      or my deployment descriptors which obviously need to know something :-).

                      Typical use cases that I know about:
                      * Create a proxy for these interfaces over this target in this domain
                      * As above, but include something in the metadata for the advice stack
                      * Take this object and add @MBean to it giving me back the enhanced object/proxy that implements DynamicMBean
                      * Create me a remote proxy for this target object

                      Something like (most parameters are optional):
                      public interface ProxyFactory
                      {
                      Object createProxy() throws Exception
                      (
                      Object identity,
                      ClassLoader cl,
                      Class[] interfaces,
                      Object target,
                      AspectManager domain,
                      MetaDataResolver instanceData,
                      AnnotationMetaData annotationOverrides
                      );
                      }

                      • 8. Re:
                        Adrian Brock Master

                         


                        A common proxy/ih, or optimized invocations.


                        I am not saying there should be a one proxy factory fits all solution.
                        But equally, I don't want everybody tailoring their own solution by hand.

                        The latter is too expensive to maintain and unnecessary in most cases.
                        Just let people "buy it off the peg". :-)

                        • 9. Re:
                          Adrian Brock Master

                          I've committed my little prototype in the aspects module
                          under org.jboss.aop.microcontainer.prototype

                          I believe this is less functional in general than the work you have already done
                          but it meets my needs (for now) of "aspectizing" and "pojoizing" jca
                          as a mechanism to develop other issues without having to resolve lower level details.

                          The two approach's feature sets need merging together, developing this prototype
                          has already shown me there is a fundamental issue with the way the MC currently
                          expects to resolve contextual aspect config. See the TODOs.

                          The changes are backwardly compatibile (simple if statements that go through the old
                          path unless you are using my prototype advisor). The major hack is this method
                          (which I've deprecated immediately) in the advisor:

                          
                           /**
                           * @deprecated Need a better mechanism to override the
                           * methods seen by pointcuts, e.g. those provided
                           * by a "proxy advisor"
                           */
                           public Method[] getAllMethods()
                           {
                           return null;
                           }