You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwebbeans.apache.org by st...@apache.org on 2012/12/31 02:19:46 UTC

svn commit: r1427033 - in /openwebbeans/trunk/webbeans-impl/src: main/java/org/apache/webbeans/proxy/asm/ test/java/org/apache/webbeans/newtests/interceptors/factory/ test/java/org/apache/webbeans/newtests/interceptors/factory/beans/

Author: struberg
Date: Mon Dec 31 01:19:45 2012
New Revision: 1427033

URL: http://svn.apache.org/viewvc?rev=1427033&view=rev
Log:
OWB-344 simple interceptor who 1:1 delegates through

the great thing: without reflection but with pure bytecode magic

Modified:
    openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/proxy/asm/InterceptorDecoratorProxyFactory.java
    openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java
    openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/beans/ClassInterceptedClass.java

Modified: openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/proxy/asm/InterceptorDecoratorProxyFactory.java
URL: http://svn.apache.org/viewvc/openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/proxy/asm/InterceptorDecoratorProxyFactory.java?rev=1427033&r1=1427032&r2=1427033&view=diff
==============================================================================
--- openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/proxy/asm/InterceptorDecoratorProxyFactory.java (original)
+++ openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/proxy/asm/InterceptorDecoratorProxyFactory.java Mon Dec 31 01:19:45 2012
@@ -20,8 +20,15 @@ package org.apache.webbeans.proxy.asm;
 
 
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.webbeans.proxy.ProxyGenerationException;
 import org.objectweb.asm.ClassWriter;
@@ -39,10 +46,43 @@ import org.objectweb.asm.Type;
  * All intercepted and decorated methods will get invoked via an InvocationHandler chain.
  *
  * This factory will create and cache the proxy classes for a given type.
+ *
+ * TODO: clarify how serialisation works! The proxy classes might need to get created on deserialisation!
  */
 public class InterceptorDecoratorProxyFactory
 {
 
+    /** the name of the field which stores the proxied instance */
+    public static final String FIELD_PROXIED_INSTANCE = "owbIntDecProxiedInstance";
+
+    public <T> T createProxyInstance(Class<T> proxyClass, T instance)
+            throws ProxyGenerationException
+    {
+        try
+        {
+            T proxy = proxyClass.newInstance();
+
+            Field delegateField = proxy.getClass().getDeclaredField(FIELD_PROXIED_INSTANCE);
+            delegateField.setAccessible(true);
+            delegateField.set(proxy, instance);
+
+            return proxy;
+        }
+        catch (InstantiationException e)
+        {
+            throw new ProxyGenerationException(e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new ProxyGenerationException(e);
+        }
+        catch (NoSuchFieldException e)
+        {
+            throw new ProxyGenerationException(e);
+        }
+    }
+
+
     /**
      * Create a decorator and interceptor proxy for the given type.
      *
@@ -57,43 +97,66 @@ public class InterceptorDecoratorProxyFa
      * @return the proxy class
      */
     public synchronized <T> Class<T> createInterceptorDecoratorProxyClass(ClassLoader classLoader, Class<T> classToProxy)
+            throws ProxyGenerationException
     {
-        String proxyName = classToProxy.getName() + "$OwbInterceptProxy";
-        String proxyClassFileName = proxyName.replace('.', '/');
+        String proxyClassName = classToProxy.getName() + "$OwbInterceptProxy";
+        String proxyClassFileName = proxyClassName.replace('.', '/');
 
-        try
-        {
-            final byte[] proxyBytes = generateProxy(classToProxy, proxyName, proxyClassFileName);
-            return defineAndLoadClass(classLoader, proxyName, proxyBytes);
-        }
-        catch (Exception e)
-        {
-            final InternalError internalError = new InternalError();
-            internalError.initCause(e);
-            throw internalError;
-        }
+        final byte[] proxyBytes = generateProxy(classToProxy, proxyClassName, proxyClassFileName);
+        return defineAndLoadClass(classLoader, proxyClassName, proxyBytes);
     }
 
 
-    private byte[] generateProxy(Class<?> classToProxy, String proxyName, String proxyClassFileName)
+    private byte[] generateProxy(Class<?> classToProxy, String proxyClassName, String proxyClassFileName)
             throws ProxyGenerationException
     {
         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
         String classFileName = classToProxy.getName().replace('.', '/');
 
-        String[] interfaceNames = new String[0]; //X TODO there might be some more in the future
+        String[] interfaceNames = new String[]{}; //X TODO there might be some more in the future
 
         cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER + Opcodes.ACC_SYNTHETIC, proxyClassFileName, null, classFileName, interfaceNames);
         cw.visitSource(classFileName + ".java", null);
 
-        propagateDefaultConstructor(cw, classToProxy, classFileName);
+        createInstanceVariables(cw, classToProxy, classFileName);
+
+        createConstructor(cw, proxyClassFileName, classToProxy, classFileName);
+
+
+        //X TODO filter out clone() and handle it seperately?
+        Map<String, List<Method>> methodMap = getNonPrivateMethods(classToProxy);
+
+        //X TODO select all non-intercepted and non-decorated methods
+
+        delegateNonInterceptedMethods(cw, proxyClassFileName, classToProxy, classFileName, methodMap);
+
+
+
+        //X TODO invoke all
 
         //X TODO continue;
 
         return cw.toByteArray();
     }
 
-    private void propagateDefaultConstructor(ClassWriter cw, Class<?> classToProxy, String classFileName)
+    private void createInstanceVariables(ClassWriter cw, Class<?> classToProxy, String classFileName)
+    {
+        // variable #1
+        createDelegationPoint(cw, classToProxy, classFileName);
+    }
+
+    /**
+     * Each of our interceptor/decorator proxies has exactly 1 constructor
+     * which invokes the super ct + sets the delegation field.
+     *
+     * //X TODO set delegation instance
+     *
+     * @param cw
+     * @param classToProxy
+     * @param classFileName
+     * @throws ProxyGenerationException
+     */
+    private void createConstructor(ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, String classFileName)
             throws ProxyGenerationException
     {
         try
@@ -104,8 +167,12 @@ public class InterceptorDecoratorProxyFa
             final MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", descriptor, null, null);
             mv.visitCode();
             mv.visitVarInsn(Opcodes.ALOAD, 0);
-
             mv.visitMethodInsn(Opcodes.INVOKESPECIAL, classFileName, "<init>", descriptor);
+
+            mv.visitVarInsn(Opcodes.ALOAD, 0);
+            mv.visitInsn(Opcodes.ACONST_NULL);
+            mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassFileName, FIELD_PROXIED_INSTANCE, Type.getDescriptor(classToProxy));
+
             mv.visitInsn(Opcodes.RETURN);
             mv.visitMaxs(-1, -1);
             mv.visitEnd();
@@ -117,6 +184,134 @@ public class InterceptorDecoratorProxyFa
     }
 
     /**
+     * Create the private field to point to the internal contextual instance.
+     * @param cw
+     * @param classToProxy
+     * @param classFileName
+     */
+    private void createDelegationPoint(ClassWriter cw, Class<?> classToProxy, String classFileName)
+    {
+        cw.visitField(Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE, FIELD_PROXIED_INSTANCE, Type.getDescriptor(classToProxy), null, null).visitEnd();
+    }
+
+
+    private Map<String, List<Method>> getNonPrivateMethods(Class<?> clazz)
+    {
+        Map<String, List<Method>> methodMap = new HashMap<String, List<Method>>();
+
+        while (clazz != null)
+        {
+            for (Method method : clazz.getDeclaredMethods())
+            {
+                final int modifiers = method.getModifiers();
+
+                if (Modifier.isFinal(modifiers) || Modifier.isPrivate(modifiers) ||
+                    Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers) ||
+                    Modifier.isNative(modifiers)) //X TODO deal with proxying native methods (clone) later
+                {
+                    continue;
+                }
+
+                if ("finalize".equals(method.getName()))
+                {
+                    // we do not proxy finalize()
+                    continue;
+                }
+
+                List<Method> methods = methodMap.get(method.getName());
+                if (methods == null)
+                {
+                    methods = new ArrayList<Method>();
+                    methods.add(method);
+                    methodMap.put(method.getName(), methods);
+                }
+                else
+                {
+                    if (isOverridden(methods, method))
+                    {
+                        // method is overridden in superclass, so do nothing
+                    }
+                    else
+                    {
+                        // method is not overridden, so add it
+                        methods.add(method);
+                    }
+                }
+            }
+
+            clazz = clazz.getSuperclass();
+        }
+
+        return methodMap;
+    }
+
+    private boolean isOverridden(final List<Method> methods, final Method method)
+    {
+        for (final Method m : methods)
+        {
+            if (Arrays.equals(m.getParameterTypes(), method.getParameterTypes()))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Directly delegate all non intercepted nor decorated methods to the internal instance.
+     *
+     * @param noninterceptedMethods all methods which are neither intercepted nor decorated
+     */
+    private static void delegateNonInterceptedMethods(ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, String classFileName,
+                                                      Map<String, List<Method>> noninterceptedMethods)
+    {
+        for (List<Method> methodsPerName : noninterceptedMethods.values())
+        {
+            for (Method proxiedMethod : methodsPerName)
+            {
+                String methodDescriptor = Type.getMethodDescriptor(proxiedMethod);
+
+                //X TODO handle generic exception types?
+                Class[] exceptionTypes = proxiedMethod.getExceptionTypes();
+                String[] exceptionTypeNames = new String[exceptionTypes.length];
+                for (int i = 0; i < exceptionTypes.length; i++)
+                {
+                    exceptionTypeNames[i] = Type.getType(exceptionTypes[i]).getInternalName();
+                }
+
+                MethodVisitor mv = cw.visitMethod(proxiedMethod.getModifiers(), proxiedMethod.getName(), methodDescriptor, null, exceptionTypeNames);
+
+                // fill method body
+                mv.visitCode();
+
+                // load the delegate variable
+                mv.visitVarInsn(Opcodes.ALOAD, 0);
+                mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassFileName, FIELD_PROXIED_INSTANCE, Type.getDescriptor(classToProxy));
+
+                int offset = 1;
+                for (Class<?> aClass : proxiedMethod.getParameterTypes())
+                {
+                    final Type type = Type.getType(aClass);
+                    mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), offset);
+                    offset += type.getSize();
+                }
+
+                final Type declaringClass = Type.getType(proxiedMethod.getDeclaringClass());
+                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, declaringClass.getInternalName(), proxiedMethod.getName(), methodDescriptor);
+
+                final Type returnType = Type.getType(proxiedMethod.getReturnType());
+                mv.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+                mv.visitMaxs(-1, -1);
+
+                mv.visitEnd();
+
+            }
+        }
+    }
+
+
+    /**
      * The 'defineClass' method on the ClassLoader is protected, thus we need to invoke it via reflection.
      * @return the Class which got loaded in the classloader
      */
@@ -150,7 +345,15 @@ public class InterceptorDecoratorProxyFa
         {
             Class<T> definedClass = (Class<T>) defineClassMethod.invoke(classLoader, proxyName, proxyBytes, 0, proxyBytes.length);
 
-            return definedClass;
+            try
+            {
+                Class<T> loadedClass = (Class<T>) classLoader.loadClass(definedClass.getName());
+                return loadedClass;
+            }
+            catch (ClassNotFoundException e)
+            {
+                throw new ProxyGenerationException(e);
+            }
         }
         catch (IllegalAccessException e)
         {

Modified: openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java
URL: http://svn.apache.org/viewvc/openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java?rev=1427033&r1=1427032&r2=1427033&view=diff
==============================================================================
--- openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java (original)
+++ openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java Mon Dec 31 01:19:45 2012
@@ -18,6 +18,8 @@
  */
 package org.apache.webbeans.newtests.interceptors.factory;
 
+import java.lang.reflect.Field;
+
 import org.apache.webbeans.newtests.interceptors.factory.beans.ClassInterceptedClass;
 import org.apache.webbeans.proxy.asm.InterceptorDecoratorProxyFactory;
 
@@ -41,12 +43,20 @@ public class InterceptorDecoratorProxyFa
         Class<ClassInterceptedClass> proxyClass = pf.createInterceptorDecoratorProxyClass(classLoader, ClassInterceptedClass.class);
         Assert.assertNotNull(proxyClass);
 
-        ClassInterceptedClass instance = proxyClass.newInstance();
-        Assert.assertNotNull(instance);
-        Assert.assertTrue(instance.defaultCtInvoked);
+        ClassInterceptedClass proxy = pf.createProxyInstance(proxyClass, new ClassInterceptedClass());
+        Assert.assertNotNull(proxy);
+
+        // we need to get the field from the proxy via reflection
+        // otherwise we will end up seeing the proxied method on the internal state
+        Field field = proxy.getClass().getSuperclass().getDeclaredField("defaultCtInvoked");
+        Assert.assertNotNull(field);
+        field.setAccessible(true);
+
+        Boolean isDefaultCtInvoked = (Boolean) field.get(proxy);
+        Assert.assertTrue(isDefaultCtInvoked);
 
-        instance.setMeaningOfLife(42);
+        proxy.setMeaningOfLife(42);
 
-        Assert.assertEquals(42, instance.getMeaningOfLife());
+        Assert.assertEquals(42, proxy.getMeaningOfLife());
     }
 }

Modified: openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/beans/ClassInterceptedClass.java
URL: http://svn.apache.org/viewvc/openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/beans/ClassInterceptedClass.java?rev=1427033&r1=1427032&r2=1427033&view=diff
==============================================================================
--- openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/beans/ClassInterceptedClass.java (original)
+++ openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/newtests/interceptors/factory/beans/ClassInterceptedClass.java Mon Dec 31 01:19:45 2012
@@ -26,7 +26,7 @@ import org.apache.webbeans.test.componen
 @Transactional
 public class ClassInterceptedClass
 {
-    public boolean defaultCtInvoked = false;
+    private boolean defaultCtInvoked = false;
 
     private int meaningOfLife;
 
@@ -37,6 +37,8 @@ public class ClassInterceptedClass
 
     public int getMeaningOfLife()
     {
+        System.out.println("answering the question about life, the universe and everything!");
+        System.out.println("and being in " + this.getClass());
         return meaningOfLife;
     }
 
@@ -44,4 +46,5 @@ public class ClassInterceptedClass
     {
         this.meaningOfLife = meaningOfLife;
     }
+
 }