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);
+ }
+ }
}