Author: rmannibucau
Date: Wed Jul 16 18:15:10 2014
New Revision: 1611123

URL: http://svn.apache.org/r1611123
Log:
trying to force deploy time enhancement for cmp beans, looks hacky, not sure 
we'll backport it for jpa before openjpa uses asm correctly

Modified:
    
tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/CmpJarBuilder.java

Modified: 
tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/CmpJarBuilder.java
URL: 
http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/CmpJarBuilder.java?rev=1611123&r1=1611122&r2=1611123&view=diff
==============================================================================
--- 
tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/CmpJarBuilder.java
 (original)
+++ 
tomee/tomee/trunk/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/CmpJarBuilder.java
 Wed Jul 16 18:15:10 2014
@@ -17,6 +17,8 @@
 
 package org.apache.openejb.assembler.classic;
 
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
 import org.apache.openejb.ClassLoaderUtil;
 import org.apache.openejb.core.cmp.CmpUtil;
 import org.apache.openejb.core.cmp.cmp2.Cmp1Generator;
@@ -27,15 +29,41 @@ import org.apache.openejb.loader.SystemI
 import org.apache.openejb.util.LogCategory;
 import org.apache.openejb.util.Logger;
 import org.apache.openejb.util.UrlCache;
-
+import org.apache.openejb.util.classloader.URLClassLoaderFirst;
+import org.apache.openjpa.enhance.PCEnhancer;
+import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
+import org.apache.openjpa.jdbc.meta.MappingRepository;
+import org.apache.openjpa.jdbc.meta.NoneMappingDefaults;
+import org.apache.openjpa.jdbc.sql.HSQLDictionary;
+import org.apache.openjpa.lib.util.BytecodeWriter;
+import org.apache.openjpa.meta.AccessCode;
+import org.apache.openjpa.meta.MetaDataModes;
+import org.apache.openjpa.meta.MetaDataRepository;
+import org.apache.openjpa.persistence.jdbc.PersistenceMappingFactory;
+import org.apache.xbean.asm5.ClassReader;
+import org.apache.xbean.asm5.ClassWriter;
+import serp.bytecode.BCClass;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.TreeSet;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
 
+import static java.util.Arrays.asList;
+
 /**
  * Creates a jar file which contains the CMP implementation classes and the 
cmp entity mappings xml file.
  */
@@ -80,16 +108,34 @@ public class CmpJarBuilder {
             jarOutputStream = openJarFile(this);
 
             // Generate CMP implementation classes
+            final Map<String, Entry> classes = new HashMap<>();
             for (final EjbJarInfo ejbJar : appInfo.ejbJars) {
                 for (final EnterpriseBeanInfo beanInfo : 
ejbJar.enterpriseBeans) {
                     if (beanInfo instanceof EntityBeanInfo) {
                         final EntityBeanInfo entityBeanInfo = (EntityBeanInfo) 
beanInfo;
                         if 
("CONTAINER".equalsIgnoreCase(entityBeanInfo.persistenceType)) {
-                            generateClass(jarOutputStream, entityBeanInfo);
+                            final Entry entry = generateClass(jarOutputStream, 
entityBeanInfo);
+                            classes.put(entry.clazz, entry);
                         }
                     }
                 }
             }
+
+            final URLClassLoaderFirst thisClassLoader = new 
EnhancingClassLoader(tempClassLoader, classes);
+            final StoringBytecodeBytecodeWriter writer = new 
StoringBytecodeBytecodeWriter(thisClassLoader);
+            doEnhanceWithOpenJPA(classes, thisClassLoader, writer, 
appInfo.cmpMappingsXml);
+
+            for (final Entry e : classes.values()) {
+                // add the generated class to the jar
+                final byte[] bytes = writer.bytecodes.get(e.clazz);
+                final byte[] bytecode = bytes != null ? bytes : e.bytes;
+                final File f = new File("/tmp/dump/" + e.name + ".class");
+                f.getParentFile().mkdirs();
+                final FileOutputStream w = new FileOutputStream(f);
+                w.write(bytecode);
+                w.close();
+                addJarEntry(jarOutputStream, e.name, bytecode);
+            }
             if (appInfo.cmpMappingsXml != null) {
                 // System.out.println(appInfo.cmpMappingsXml);
                 addJarEntry(jarOutputStream, 
"META-INF/openejb-cmp-generated-orm.xml", appInfo.cmpMappingsXml.getBytes());
@@ -107,6 +153,68 @@ public class CmpJarBuilder {
         }
     }
 
+    private void doEnhanceWithOpenJPA(final Map<String, Entry> classes, final 
ClassLoader tmpLoader,
+                                      final StoringBytecodeBytecodeWriter 
writer,
+                                      final String cmpMappingsXml) throws 
ClassNotFoundException, IOException {
+        final Thread th = Thread.currentThread();
+        final ClassLoader old = th.getContextClassLoader();
+        th.setContextClassLoader(tmpLoader);
+        try {
+            final JDBCConfigurationImpl conf = new JDBCConfigurationImpl();
+            conf.setDBDictionary(new HSQLDictionary());
+            final MappingRepository repos = new MappingRepository();
+
+            final Set<Class<?>> tmpClasses = new HashSet<>();
+            for (final Entry e : classes.values()) {
+                tmpClasses.add(tmpLoader.loadClass(e.clazz));
+            }
+
+            final PersistenceMappingFactory factory = new 
PersistenceMappingFactory() {
+                @Override
+                public Set getPersistentTypeNames(final boolean devpath, final 
ClassLoader envLoader) {
+                    getXMLParser().setValidating(false);
+                    try { // xml only
+                        return parsePersistentTypeNames(tmpLoader);
+                    } catch (final IOException e) {
+                        // no-op
+                    }
+                    return super.getPersistentTypeNames(devpath, envLoader);
+                }
+            };
+
+            final File tempFile = File.createTempFile("OpenEJBGenerated.", 
".xml", tmpDir());
+            tempFile.deleteOnExit();
+            final FileWriter tmpMapping = new FileWriter(tempFile);
+            try {
+                tmpMapping.write(cmpMappingsXml);
+            } finally {
+                tmpMapping.close();
+            }
+            factory.setFiles(asList(tempFile));
+
+            repos.setConfiguration(conf);
+            repos.setMetaDataFactory(factory);
+            repos.setMappingDefaults(NoneMappingDefaults.getInstance());
+            repos.setResolve(MetaDataModes.MODE_NONE);
+            repos.setValidate(MetaDataRepository.VALIDATE_NONE);
+            for (final Class<?> tmpClass : tmpClasses) {
+                repos.addMetaData(tmpClass);
+            }
+
+            final PCEnhancer.Flags flags = new PCEnhancer.Flags();
+            flags.tmpClassLoader = false;
+
+            PCEnhancer.run(conf, null, flags, repos, writer, tmpLoader);
+
+            tempFile.delete(); // try to delete it now, not a big deal 
otherwise,deleteOnExit will do it
+        } catch (final Throwable thr) {
+            // shouldn't be created in normal case
+            Logger.getInstance(LogCategory.OPENEJB, 
CmpJarBuilder.class).error(thr.getMessage(), thr);
+        } finally {
+            th.setContextClassLoader(old);
+        }
+    }
+
     /**
      * Test if an application contains and CMP beans that
      * need to be mapped to the JPA persistence engine.  This
@@ -140,12 +248,12 @@ public class CmpJarBuilder {
      * @param entityBeanInfo  The descriptor for the entity bean we need to 
wrapper.
      * @throws IOException
      */
-    private void generateClass(final JarOutputStream jarOutputStream, final 
EntityBeanInfo entityBeanInfo) throws IOException {
+    private Entry generateClass(final JarOutputStream jarOutputStream, final 
EntityBeanInfo entityBeanInfo) throws IOException {
         // don't generate if there is aleady an implementation class
         final String cmpImplClass = 
CmpUtil.getCmpImplClassName(entityBeanInfo.abstractSchemaName, 
entityBeanInfo.ejbClass);
         final String entryName = cmpImplClass.replace(".", "/") + ".class";
         if (entries.contains(entryName) || 
tempClassLoader.getResource(entryName) != null) {
-            return;
+            return null;
         }
 
         // load the bean class, which is used by the generator
@@ -201,8 +309,7 @@ public class CmpJarBuilder {
             bytes = cmp2Generator.generate();
         }
 
-        // add the generated class to the jar
-        addJarEntry(jarOutputStream, entryName, bytes);
+        return new Entry(cmpImplClass, entryName, bytes);
     }
 
     /**
@@ -245,11 +352,7 @@ public class CmpJarBuilder {
             throw new IllegalStateException("Jar file exists already");
         }
 
-        File dir = UrlCache.cacheDir;
-
-        if (null == dir) {
-            dir = SystemInstance.get().getBase().getDirectory("tmp", true);
-        }
+        final File dir = tmpDir();
 
         // if url caching is enabled, generate the file directly in the cache 
dir, so it doesn't have to be recoppied
         try {
@@ -277,6 +380,15 @@ public class CmpJarBuilder {
         return new JarOutputStream(IO.write(instance.jarFile));
     }
 
+    private static File tmpDir() throws IOException {
+        File dir = UrlCache.cacheDir;
+
+        if (null == dir) {
+            dir = SystemInstance.get().getBase().getDirectory("tmp", true);
+        }
+        return dir;
+    }
+
     private void close(final JarOutputStream jarOutputStream) {
         if (jarOutputStream != null) {
             try {
@@ -286,4 +398,114 @@ public class CmpJarBuilder {
             }
         }
     }
+
+    private static class Entry {
+        private final String clazz;
+        private final String name;
+        private final byte[] bytes;
+
+        private Entry(final String clazz, final String name, final byte[] 
bytes) {
+            this.clazz = clazz;
+            this.name = name;
+            this.bytes = bytes;
+        }
+    }
+
+    private static class StoringBytecodeBytecodeWriter implements 
BytecodeWriter {
+        private final Map<String, byte[]> bytecodes = new HashMap<>();
+        private final ClassLoader loader;
+
+        private StoringBytecodeBytecodeWriter(final ClassLoader loader) {
+            this.loader = loader;
+        }
+
+        @Override
+        public void write(final BCClass type) throws IOException {
+            bytecodes.put(type.getName(), type.toByteArray());
+
+            final byte[] b = type.toByteArray();
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_7)) {
+                final ByteArrayInputStream bais = new ByteArrayInputStream(b);
+                final BufferedInputStream bis = new BufferedInputStream(bais);
+                final ClassWriter cw = new CommonClassWriterHack(loader);
+                final ClassReader cr = new ClassReader(bis);
+                cr.accept(cw, 0);
+                baos.write(cw.toByteArray());
+            } else {
+                baos.write(b);
+            }
+        }
+    }
+
+    private static class CommonClassWriterHack extends ClassWriter {
+        private final ClassLoader loader;
+
+        private CommonClassWriterHack(final ClassLoader loader) {
+            super(ClassWriter.COMPUTE_FRAMES);
+            this.loader = loader;
+        }
+
+        @Override
+        protected String getCommonSuperClass(final String type1, final String 
type2) {
+            Class<?> class1;
+            Class<?> class2;
+            try {
+                class1 = loader.loadClass(type1.replace('/', '.'));
+                class2 = loader.loadClass(type2.replace('/', '.'));
+            } catch (final ClassNotFoundException ex) {
+                throw new RuntimeException(ex);
+            }
+            if (class1.isAssignableFrom(class2)) {
+                return type1;
+            }
+            if (class2.isAssignableFrom(class1)) {
+                return type2;
+            }
+            if (class1.isInterface() || class2.isInterface()) {
+                return "java/lang/Object";
+            }
+            do {
+                class1 = class1.getSuperclass();
+            } while (!class1.isAssignableFrom(class2));
+            return class1.getName().replace('.', '/');
+        }
+    }
+
+    private static class EnhancingClassLoader extends URLClassLoaderFirst {
+        private final Map<String, Entry> classes;
+
+        public EnhancingClassLoader(final ClassLoader tempClassLoader, 
Map<String, Entry> classes) {
+            super(new URL[0], tempClassLoader);
+            this.classes = classes;
+        }
+
+        @Override
+        public Class<?> loadClass(final String name, final boolean resolve) 
throws ClassNotFoundException {
+            final Entry e = classes.get(name);
+            if (e != null) {
+                final Class<?> alreadyLoaded = findLoadedClass(name);
+                if (alreadyLoaded != null) {
+                    if (resolve) {
+                        resolveClass(alreadyLoaded);
+                    }
+                    return alreadyLoaded;
+                }
+
+                final Class<?> c = defineClass(e.clazz, e.bytes, 0, 
e.bytes.length);
+                if (resolve) {
+                    resolveClass(c);
+                }
+                return c;
+            }
+            return super.loadClass(name, resolve);
+        }
+
+        @Override
+        public InputStream getResourceAsStream(final String name) {
+            final String key = name.replace('/', '.').replace(".class", "");
+            final Entry e = classes.get(key);
+            return e != null ? new ByteArrayInputStream(e.bytes) : 
super.getResourceAsStream(name);
+        }
+    }
 }


Reply via email to