You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by st...@apache.org on 2023/05/06 07:18:31 UTC

[openjpa] 06/17: OPENJPA-2909 first skeleton for ASM based proxies

This is an automated email from the ASF dual-hosted git repository.

struberg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openjpa.git

commit 487159da3bc5404a39e87a883410405e1ef39b02
Author: Mark Struberg <st...@apache.org>
AuthorDate: Mon May 1 20:24:34 2023 +0200

    OPENJPA-2909 first skeleton for ASM based proxies
    
    work in progress
---
 .../openjpa/util/ClassLoaderProxyService.java      | 154 +++++++++++++
 .../org/apache/openjpa/util/GeneratedClasses.java  |   6 +
 .../org/apache/openjpa/util/ProxyManagerImpl.java  | 246 +++++++++++++++++++--
 3 files changed, 393 insertions(+), 13 deletions(-)

diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ClassLoaderProxyService.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ClassLoaderProxyService.java
new file mode 100644
index 000000000..654d06368
--- /dev/null
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ClassLoaderProxyService.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.util;
+
+import java.security.ProtectionDomain;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.openjpa.lib.conf.Configuration;
+
+/**
+ * Service to load classes dynamically at runtime.
+ * This class got forked from Apache OpenWebBeans
+ */
+public class ClassLoaderProxyService
+{
+    private final ProxiesClassLoader loader;
+
+    public ClassLoaderProxyService(Configuration config, final ClassLoader parentLoader)
+    {
+        this.loader = new ProxiesClassLoader(parentLoader, false);
+    }
+
+    protected ClassLoaderProxyService(final ProxiesClassLoader loader)
+    {
+        this.loader = loader;
+    }
+
+    public ClassLoader getProxyClassLoader(final Class<?> forClass)
+    {
+        return loader;
+    }
+
+    public <T> Class<T> defineAndLoad(final String name, final byte[] bytecode, final Class<T> proxiedClass)
+    {
+        return (Class<T>) loader.getOrRegister(
+                name, bytecode, proxiedClass.getPackage(), proxiedClass.getProtectionDomain());
+    }
+
+    public <T> T newInstance(final Class<? extends T> proxyClass)
+    {
+        try
+        {
+            return proxyClass.getConstructor().newInstance();
+        }
+        catch (final Exception e)
+        {
+            throw new IllegalStateException("Failed to create a new Proxy instance of " + proxyClass.getName(), e);
+        }
+    }
+
+
+    private static class ProxiesClassLoader extends ClassLoader
+    {
+        private final boolean skipPackages;
+        private final ConcurrentMap<String, Class<?>> classes = new ConcurrentHashMap<>();
+
+        private ProxiesClassLoader(final ClassLoader parentLoader, boolean skipPackages)
+        {
+            super(parentLoader);
+            this.skipPackages = skipPackages;
+        }
+
+
+        @Override
+        protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException
+        {
+            final Class<?> clazz = classes.get(name);
+            if (clazz == null)
+            {
+                return getParent().loadClass(name);
+            }
+            return clazz;
+        }
+
+        private Class<?> getOrRegister(final String proxyClassName, final byte[] proxyBytes,
+                                       final Package pck, final ProtectionDomain protectionDomain)
+        {
+            final String key = proxyClassName.replace('/', '.');
+            Class<?> existing = classes.get(key);
+            if (existing == null)
+            {
+                synchronized (this)
+                {
+                    existing = classes.get(key);
+                    if (existing == null)
+                    {
+                        if (!skipPackages)
+                        {
+                            definePackageFor(pck, protectionDomain);
+                        }
+                        existing = super.defineClass(proxyClassName, proxyBytes, 0, proxyBytes.length);
+                        resolveClass(existing);
+                        classes.put(key, existing);
+                    }
+                }
+            }
+            return existing;
+        }
+
+        private void definePackageFor(final Package model, final ProtectionDomain protectionDomain)
+        {
+            if (model == null)
+            {
+                return;
+            }
+            if (getPackage(model.getName()) == null)
+            {
+                if (model.isSealed() && protectionDomain != null &&
+                        protectionDomain.getCodeSource() != null &&
+                        protectionDomain.getCodeSource().getLocation() != null)
+                {
+                    definePackage(
+                            model.getName(),
+                            model.getSpecificationTitle(),
+                            model.getSpecificationVersion(),
+                            model.getSpecificationVendor(),
+                            model.getImplementationTitle(),
+                            model.getImplementationVersion(),
+                            model.getImplementationVendor(),
+                            protectionDomain.getCodeSource().getLocation());
+                }
+                else
+                {
+                    definePackage(
+                            model.getName(),
+                            model.getSpecificationTitle(),
+                            model.getSpecificationVersion(),
+                            model.getSpecificationVendor(),
+                            model.getImplementationTitle(),
+                            model.getImplementationVersion(),
+                            model.getImplementationVendor(),
+                            null);
+                }
+            }
+        }
+    }
+}
diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneratedClasses.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneratedClasses.java
index b109b2734..47e5961d2 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneratedClasses.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/GeneratedClasses.java
@@ -56,6 +56,7 @@ public class GeneratedClasses {
 
     /**
      * Load the class represented by the given bytecode.
+     * @deprecated move to ASM
      */
     public static Class loadBCClass(BCClass bc, ClassLoader loader) {
         BCClassLoader bcloader = AccessController
@@ -70,6 +71,11 @@ public class GeneratedClasses {
         }
     }
 
+    public static Class loadAsmClass(String className, byte[] classBytes, Class<?> proxiedClass, ClassLoader loader) {
+        ClassLoaderProxyService pcls = new ClassLoaderProxyService(null, loader);
+        return pcls.defineAndLoad(className, classBytes, proxiedClass);
+    }
+
     /**
      * Return true if the given loader will load the same version of a given
      * class.
diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java
index 0737d1ef2..5d41ffa2e 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java
@@ -73,6 +73,10 @@ import org.apache.openjpa.util.proxy.ProxyConcurrentMaps;
 import org.apache.openjpa.util.proxy.ProxyDate;
 import org.apache.openjpa.util.proxy.ProxyMap;
 import org.apache.openjpa.util.proxy.ProxyMaps;
+import org.apache.xbean.asm9.ClassWriter;
+import org.apache.xbean.asm9.MethodVisitor;
+import org.apache.xbean.asm9.Opcodes;
+import org.apache.xbean.asm9.Type;
 
 import serp.bytecode.BCClass;
 import serp.bytecode.BCField;
@@ -85,6 +89,7 @@ import serp.bytecode.Project;
  * Default implementation of the {@link ProxyManager} interface.
  *
  * @author Abe White
+ * @author Mark Struberg
  */
 public class ProxyManagerImpl
     implements ProxyManager {
@@ -446,8 +451,7 @@ public class ProxyManagerImpl
                 ProxyDate.class);
             Class pcls = loadBuildTimeProxy(type, l);
             if (pcls == null)
-                pcls = GeneratedClasses.loadBCClass(
-                    generateProxyDateBytecode(type, true), l);
+                pcls = generateAndLoadProxyDate(type, true, l);
             proxy = (ProxyDate) instantiateProxy(pcls, null, null);
             _proxies.put(type, proxy);
         }
@@ -664,25 +668,235 @@ public class ProxyManagerImpl
         return bc;
     }
 
+
+    private Class generateAndLoadProxyDate(Class type, boolean runtime, ClassLoader l) {
+        final String proxyClassName = getProxyClassName(type, runtime);
+        final byte[] classBytes = generateProxyDateBytecode(type, runtime, proxyClassName);
+
+        return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyDate.class, l);
+    }
+
     /**
      * Generate the bytecode for a date proxy for the given type.
      */
-    protected BCClass generateProxyDateBytecode(Class type, boolean runtime) {
+    protected byte[] generateProxyDateBytecode(Class type, boolean runtime, String proxyClassName) {
         assertNotFinal(type);
-        Project project = new Project();
-        BCClass bc = AccessController.doPrivileged(J2DoPrivHelper
-            .loadProjectClassAction(project, getProxyClassName(type, runtime)));
-        bc.setSuperclass(type);
-        bc.declareInterface(ProxyDate.class);
+        proxyClassName = proxyClassName.replace('.', '/');
 
-        delegateConstructors(bc, type);
-        addProxyMethods(bc, true);
+        String superClassFileNname = type.getName().replace('.', '/');
+
+        String[] interfaceNames = new String[]{Type.getInternalName(ProxyDate.class)};
+
+        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+        cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, proxyClassName,
+                null, superClassFileNname, interfaceNames);
+
+        String classFileName = runtime ? type.getName() : proxyClassName;
+        cw.visitSource(classFileName + ".java", null);
+
+        delegateConstructors(cw, type, superClassFileNname);
+        addInstanceVariables(cw);
+        addProxyMethods(cw, true, proxyClassName, type);
+
+/* TODO
         addProxyDateMethods(bc, type);
         proxySetters(bc, type);
         addWriteReplaceMethod(bc, runtime);
-        return bc;
+*/
+        return cw.toByteArray();
+    }
+
+    /**
+     * add the instance variables to the class to be generated
+     */
+    private void addInstanceVariables(ClassWriter cw) {
+        // variable #1, the state manager
+        cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT,
+                "sm", Type.getDescriptor(OpenJPAStateManager.class), null, null).visitEnd();
+
+        // variable #2, the state manager
+        cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT,
+                "field", Type.getDescriptor(int.class), null, null).visitEnd();
+    }
+
+    /**
+     * Returns the appropriate bytecode instruction to load a value from a variable to the stack
+     *
+     * @param type Type to load
+     * @return Bytecode instruction to use
+     */
+    private int getVarInsn(Class<?> type)
+    {
+        if (type.isPrimitive())
+        {
+            if (Integer.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Boolean.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Character.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Byte.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Short.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Float.TYPE.equals(type))
+            {
+                return Opcodes.FLOAD;
+            }
+            else if (Long.TYPE.equals(type))
+            {
+                return Opcodes.LLOAD;
+            }
+            else if (Double.TYPE.equals(type))
+            {
+                return Opcodes.DLOAD;
+            }
+        }
+
+        return Opcodes.ALOAD;
+    }
+
+    /**
+     * Create pass-through constructors to base type.
+     */
+    private void delegateConstructors(ClassWriter cw, Class type, String superClassFileNname) {
+        Constructor[] constructors = type.getConstructors();
+
+        for (Constructor constructor : constructors) {
+            Class[] params = constructor.getParameterTypes();
+            String[] exceptionTypes = getInternalNames(constructor.getExceptionTypes());
+            String descriptor = Type.getConstructorDescriptor(constructor);
+            MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", descriptor, null, exceptionTypes);
+            mv.visitCode();
+            mv.visitVarInsn(Opcodes.ALOAD, 0);
+            for (int i = 1; i <= params.length; i++)
+            {
+                mv.visitVarInsn(getVarInsn(params[i-1]), i);
+            }
+            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassFileNname, "<init>", descriptor, false);
+
+            mv.visitInsn(Opcodes.RETURN);
+            mv.visitMaxs(-1, -1);
+            mv.visitEnd();
+        }
+    }
+
+    private String[] getInternalNames(Class[] classes) {
+        String[] internalNames = new String[classes.length];
+
+        for (int i=0; i<classes.length; i++) {
+            internalNames[i] = Type.getInternalName(classes[i]);
+        }
+        return internalNames;
     }
 
+    /**
+     * Implement the methods in the {@link Proxy} interface, with the exception
+     * of {@link Proxy#copy}.
+     *
+     * @param changeTracker whether to implement a null change tracker; if false
+     * the change tracker method is left unimplemented
+     * @param proxyClassName
+     */
+    private void addProxyMethods(ClassWriter cw, boolean changeTracker, String proxyClassName, Class<?> parentClass) {
+
+        MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "setOwner",
+                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OpenJPAStateManager.class), Type.INT_TYPE)
+                , null, null);
+        mv.visitCode();
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitVarInsn(Opcodes.ALOAD, 1);
+        mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassName, "sm", Type.getDescriptor(OpenJPAStateManager.class));
+
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitVarInsn(Opcodes.ILOAD, 2);
+        mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassName, "field", Type.getDescriptor(Integer.TYPE));
+
+        mv.visitInsn(Opcodes.RETURN);
+        mv.visitMaxs(-1, -1);
+        mv.visitEnd();
+
+
+        mv = cw.visitMethod(Modifier.PUBLIC, "getOwner",
+                Type.getMethodDescriptor(Type.getType(OpenJPAStateManager.class), Type.VOID_TYPE)
+                , null, null);
+        mv.visitCode();
+
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassName, "sm", Type.getDescriptor(OpenJPAStateManager.class));
+
+        mv.visitInsn(Opcodes.IRETURN);
+        mv.visitMaxs(-1, -1);
+        mv.visitEnd();
+
+
+        mv = cw.visitMethod(Modifier.PUBLIC, "getOwnerField",
+                Type.getMethodDescriptor(Type.INT_TYPE)
+                , null, null);
+        mv.visitCode();
+
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassName, "field", Type.INT_TYPE.getDescriptor());
+
+        mv.visitInsn(Opcodes.IRETURN);
+        mv.visitMaxs(-1, -1);
+        mv.visitEnd();
+
+
+        /*
+         * clone (return detached proxy object)
+         * Note:  This method is only being provided to satisfy a quirk with
+         * the IBM JDK -- while comparing Calendar objects, the clone() method
+         * was invoked.  So, we are now overriding the clone() method so as to
+         * provide a detached proxy object (null out the StateManager).
+         */
+        mv = cw.visitMethod(Modifier.PUBLIC, "clone",
+                Type.getMethodDescriptor(Type.getType(Object.class))
+                , null, null);
+        mv.visitCode();
+
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(parentClass), "clone",
+                Type.getMethodDescriptor(Type.getType(Object.class)), false);
+        mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Proxy.class));
+        mv.visitVarInsn(Opcodes.ASTORE, 1);
+        mv.visitVarInsn(Opcodes.ALOAD, 1);
+
+        mv.visitInsn(Opcodes.ACONST_NULL);
+        mv.visitInsn(Opcodes.ICONST_0);
+        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Proxy.class), "setOwner",
+                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OpenJPAStateManager.class), Type.INT_TYPE), true);
+
+        mv.visitVarInsn(Opcodes.ALOAD, 1);
+        mv.visitInsn(Opcodes.ARETURN);
+        mv.visitMaxs(-1, -1);
+        mv.visitEnd();
+
+
+        if (changeTracker) {
+            mv = cw.visitMethod(Modifier.PUBLIC, "getChangeTracker",
+                    Type.getMethodDescriptor(Type.getType(ChangeTracker.class))
+                    , null, null);
+            mv.visitCode();
+            mv.visitInsn(Opcodes.ACONST_NULL);
+            mv.visitInsn(Opcodes.ARETURN);
+            mv.visitMaxs(-1, -1);
+            mv.visitEnd();
+        }
+
+    }
+
+
     /**
      * Generate the bytecode for a calendar proxy for the given type.
      */
@@ -1751,8 +1965,14 @@ public class ProxyManagerImpl
                 bc = mgr.generateProxyCollectionBytecode(cls, false);
             else if (Map.class.isAssignableFrom(cls))
                 bc = mgr.generateProxyMapBytecode(cls, false);
-            else if (Date.class.isAssignableFrom(cls))
-                bc = mgr.generateProxyDateBytecode(cls, false);
+            else if (Date.class.isAssignableFrom(cls)) {
+                final String proxyClassName = getProxyClassName(cls, false);
+                byte[] bytes = mgr.generateProxyDateBytecode(cls, false, proxyClassName);
+
+                final String fileName = cls.getName().replace('.', '$') + PROXY_SUFFIX + ".class";
+                java.nio.file.Files.write(new File(dir, fileName).toPath(), bytes);
+                continue;
+            }
             else if (Calendar.class.isAssignableFrom(cls))
                 bc = mgr.generateProxyCalendarBytecode(cls, false);
             else {