| /* |
| * 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.openejb.assembler.classic; |
| |
| import org.apache.openejb.ClassLoaderUtil; |
| import org.apache.openejb.core.cmp.CmpUtil; |
| import org.apache.openejb.core.cmp.cmp2.Cmp1Generator; |
| import org.apache.openejb.core.cmp.cmp2.Cmp2Generator; |
| import org.apache.openejb.core.cmp.cmp2.CmrField; |
| import org.apache.openejb.loader.IO; |
| import org.apache.openejb.loader.SystemInstance; |
| import org.apache.openejb.util.LogCategory; |
| import org.apache.openejb.util.Logger; |
| import org.apache.openejb.util.UrlCache; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.HashMap; |
| 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; |
| |
| /** |
| * Creates a jar file which contains the CMP implementation classes and the cmp entity mappings xml file. |
| */ |
| public class CmpJarBuilder { |
| |
| private final ClassLoader tempClassLoader; |
| |
| private File jarFile; |
| private final Set<String> entries = new TreeSet<String>(); |
| private final AppInfo appInfo; |
| |
| public CmpJarBuilder(final AppInfo appInfo, final ClassLoader classLoader) { |
| this.appInfo = appInfo; |
| tempClassLoader = ClassLoaderUtil.createTempClassLoader(classLoader); |
| } |
| |
| public File getJarFile() throws IOException { |
| if (jarFile == null) { |
| generate(); |
| } |
| return jarFile; |
| } |
| |
| /** |
| * Generate the CMP jar file associated with this |
| * deployed application. The generated jar file will |
| * contain generated classes and metadata that will |
| * allow the JPA engine to manage the bean persistence. |
| * |
| * @throws IOException |
| */ |
| private void generate() throws IOException { |
| // Don't generate an empty jar. If there are no container-managed beans defined in this |
| // application deployment, there's nothing to do. |
| if (!hasCmpBeans()) { |
| return; |
| } |
| |
| JarOutputStream jarOutputStream = null; |
| |
| try { |
| 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)) { |
| final Entry entry = generateClass(jarOutputStream, entityBeanInfo); |
| classes.put(entry.clazz, entry); |
| } |
| } |
| } |
| } |
| |
| for (final Entry e : classes.values()) { |
| addJarEntry(jarOutputStream, e.name, e.bytes); |
| } |
| if (appInfo.cmpMappingsXml != null) { |
| // System.out.println(appInfo.cmpMappingsXml); |
| addJarEntry(jarOutputStream, "META-INF/openejb-cmp-generated-orm.xml", appInfo.cmpMappingsXml.getBytes()); |
| } |
| } catch (final Throwable e) { |
| |
| if (null != jarFile && !jarFile.delete()) { |
| jarFile.deleteOnExit(); |
| } |
| jarFile = null; |
| |
| throw new IOException("CmpJarBuilder.generate()", e); |
| } finally { |
| close(jarOutputStream); |
| } |
| } |
| |
| /** |
| * Test if an application contains and CMP beans that |
| * need to be mapped to the JPA persistence engine. This |
| * will search all of the ejb jars contained within |
| * the application looking for Entity beans with |
| * a CONTAINER persistence type. |
| * |
| * @return true if the application uses container managed beans, |
| * false if none are found. |
| */ |
| private boolean hasCmpBeans() { |
| 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)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Generate a class file for a CMP bean, writing the |
| * byte data for the generated class into the jar file |
| * we're constructing. |
| * |
| * @param jarOutputStream The target jarfile. |
| * @param entityBeanInfo The descriptor for the entity bean we need to wrapper. |
| * @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 null; |
| } |
| |
| // load the bean class, which is used by the generator |
| Class<?> beanClass = null; |
| try { |
| beanClass = tempClassLoader.loadClass(entityBeanInfo.ejbClass); |
| } catch (final ClassNotFoundException e) { |
| throw (IOException) new IOException("Could not find entity bean class " + beanClass).initCause(e); |
| } |
| |
| // and the primary key class, if defined. |
| Class<?> primKeyClass = null; |
| if (entityBeanInfo.primKeyClass != null) { |
| try { |
| primKeyClass = tempClassLoader.loadClass(entityBeanInfo.primKeyClass); |
| } catch (final ClassNotFoundException e) { |
| throw (IOException) new IOException("Could not find entity primary key class " + entityBeanInfo.primKeyClass).initCause(e); |
| } |
| } |
| |
| // now generate a class file using the appropriate level of CMP generator. |
| final byte[] bytes; |
| // NB: We'll need to change this test of CMP 3 is ever defined! |
| if (entityBeanInfo.cmpVersion != 2) { |
| final Cmp1Generator cmp1Generator = new Cmp1Generator(cmpImplClass, beanClass); |
| // A primary key class defined as Object is an unknown key. Mark it that |
| // way so the generator will create the automatically generated key. |
| if ("java.lang.Object".equals(entityBeanInfo.primKeyClass)) { |
| cmp1Generator.setUnknownPk(true); |
| } |
| bytes = cmp1Generator.generate(); |
| } else { |
| |
| // generate the implementation class |
| final Cmp2Generator cmp2Generator = new Cmp2Generator(cmpImplClass, |
| beanClass, |
| entityBeanInfo.primKeyField, |
| primKeyClass, |
| entityBeanInfo.cmpFieldNames.toArray(new String[entityBeanInfo.cmpFieldNames.size()])); |
| |
| // we need to have a complete set of the defined CMR fields available for the |
| // generation process as well. |
| for (final CmrFieldInfo cmrFieldInfo : entityBeanInfo.cmrFields) { |
| final EntityBeanInfo roleSource = cmrFieldInfo.mappedBy.roleSource; |
| final CmrField cmrField = new CmrField(cmrFieldInfo.fieldName, |
| cmrFieldInfo.fieldType, |
| CmpUtil.getCmpImplClassName(roleSource.abstractSchemaName, roleSource.ejbClass), |
| roleSource.local, |
| cmrFieldInfo.mappedBy.fieldName, |
| cmrFieldInfo.synthetic); |
| cmp2Generator.addCmrField(cmrField); |
| } |
| bytes = cmp2Generator.generate(); |
| } |
| |
| return new Entry(cmpImplClass, entryName, bytes); |
| } |
| |
| /** |
| * Insert a file resource into the generated jar file. |
| * |
| * @param jarOutputStream The target jar file. |
| * @param fileName The name we're inserting. |
| * @param bytes The file byte data. |
| * @throws IOException |
| */ |
| private void addJarEntry(final JarOutputStream jarOutputStream, String fileName, final byte[] bytes) throws IOException { |
| // add all missing directory entries |
| fileName = fileName.replace('\\', '/'); |
| String path = ""; |
| for (final StringTokenizer tokenizer = new StringTokenizer(fileName, "/"); tokenizer.hasMoreTokens(); ) { |
| final String part = tokenizer.nextToken(); |
| if (tokenizer.hasMoreTokens()) { |
| path += part + "/"; |
| if (!entries.contains(path)) { |
| jarOutputStream.putNextEntry(new JarEntry(path)); |
| jarOutputStream.closeEntry(); |
| entries.add(path); |
| } |
| } |
| } |
| |
| // write the bytes |
| jarOutputStream.putNextEntry(new JarEntry(fileName)); |
| try { |
| jarOutputStream.write(bytes); |
| } finally { |
| jarOutputStream.closeEntry(); |
| entries.add(fileName); |
| } |
| } |
| |
| private static synchronized JarOutputStream openJarFile(final CmpJarBuilder instance) throws IOException { |
| |
| if (instance.jarFile != null) { |
| throw new IllegalStateException("Jar file exists already"); |
| } |
| |
| 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 { |
| instance.jarFile = File.createTempFile("OpenEJBGenerated.", ".jar", dir).getAbsoluteFile(); |
| } catch (final Throwable e) { |
| |
| Logger.getInstance(LogCategory.OPENEJB_STARTUP, CmpJarBuilder.class).warning("Failed to create temp jar file in: " + dir, e); |
| |
| //Try |
| try { |
| Thread.sleep(50); |
| } catch (final InterruptedException ie) { |
| //Ignore |
| } |
| |
| instance.jarFile = File.createTempFile("OpenEJBGenerated.", ".jar", dir).getAbsoluteFile(); |
| } |
| |
| Thread.yield(); |
| |
| instance.jarFile.deleteOnExit(); |
| |
| Logger.getInstance(LogCategory.OPENEJB_STARTUP, CmpJarBuilder.class).debug("Using temp jar file: " + instance.jarFile); |
| |
| 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 { |
| jarOutputStream.close(); |
| } catch (final Throwable ignored) { |
| // no-op |
| } |
| } |
| } |
| |
| private static final 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; |
| } |
| } |
| } |