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 Class defineAndLoad(final String name, final byte[] bytecode, final Class proxiedClass) + { + return (Class) loader.getOrRegister( + name, bytecode, proxiedClass.getPackage(), proxiedClass.getProtectionDomain()); + } + + public T newInstance(final Class 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> 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, "", 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, "", 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 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 {