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 {