diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java index deabe5c72..6ff969b9e 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java @@ -60,6 +60,7 @@ import javax.xml.parsers.SAXParser; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.*; public interface EJBDeploymentTool { /** @@ -87,5 +88,5 @@ public interface EJBDeploymentTool { * Configure this tool for use in the ejbjar task. */ public void configure(File srcDir, File descriptorDir, String basenameTerminator, - String baseJarName, boolean flatDestDir); + String baseJarName, boolean flatDestDir, Path classpath); } \ No newline at end of file diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java index e6c0d7f40..425ac6722 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java @@ -69,6 +69,7 @@ import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.*; /** *

Provides automated ejb jar file creation for ant. Extends the MatchingTask @@ -113,6 +114,11 @@ public class EjbJar extends MatchingTask { /** Stores a handle to the destination EJB Jar file */ private String baseJarName; + + /** + * The classpath to use when loading classes + */ + private Path classpath; /** * Instance variable that determines whether to use a package structure @@ -131,6 +137,12 @@ public class EjbJar extends MatchingTask { */ private ArrayList deploymentTools = new ArrayList(); + /** + * Create a weblogic nested element used to configure a + * deployment tool for Weblogic server. + * + * @return the deployment tool instance to be configured. + */ public WeblogicDeploymentTool createWeblogic() { WeblogicDeploymentTool tool = new WeblogicDeploymentTool(); tool.setTask(this); @@ -138,6 +150,12 @@ public class EjbJar extends MatchingTask { return tool; } + /** + * Create a nested element for weblogic when using the Toplink + * Object- Relational mapping. + * + * @return the deployment tool instance to be configured. + */ public WeblogicTOPLinkDeploymentTool createWeblogictoplink() { WeblogicTOPLinkDeploymentTool tool = new WeblogicTOPLinkDeploymentTool(); tool.setTask(this); @@ -146,7 +164,25 @@ public class EjbJar extends MatchingTask { } /** - * Setter used to store the value of srcDir prior to execute() being called. + * creates a nested classpath element. + * + * This classpath is used to locate the super classes and interfaces + * of the classes that will make up the EJB jar. + * + * @return the path to be configured. + */ + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(project); + } + return classpath.createPath(); + } + + /** + * Set the srcdir attribute. The source directory is the directory that contains + * the classes that will be added to the EJB jar. Typically this will include the + * home and remote interfaces and the bean class. + * * @param inDir the source directory. */ public void setSrcdir(File inDir) { @@ -154,7 +190,13 @@ public class EjbJar extends MatchingTask { } /** - * Setter used to store the value of descriptorDir prior to execute() being called. + * Set the descriptor directory. + * + * The descriptor directory contains the EJB deployment descriptors. These are XML + * files that declare the properties of a bean in a particular deployment scenario. Such + * properties include, for example, the transactional nature of the bean and the security + * access control to the bean's methods. + * * @param inDir the directory containing the deployment descriptors. */ public void setDescriptordir(File inDir) { @@ -162,16 +204,26 @@ public class EjbJar extends MatchingTask { } /** - * Setter used to store the value of descriptorDir prior to execute() being called. - * @param inDir the directory containing the deployment descriptors. + * Set the base name of the EJB jar that is to be created if it is not to be + * determined from the name of the deployment descriptor files. + * + * @param inValue the basename that will be used when writing the jar file containing + * the EJB */ public void setBasejarname(String inValue) { this.baseJarName = inValue; } /** - * Setter used to store the value of destination directory prior to execute() - * being called. + * Set the destination directory. + * + * The EJB jar files will be written into this directory. The jar files that exist in + * this directory are also used when determining if the contents of the jar file + * have changed. + * + * Note that this parameter is only used if no deployment tools are specified. Typically + * each deployment tool will specify its own destination directory. + * * @param inFile the destination directory. */ public void setDestdir(File inDir) { @@ -179,15 +231,37 @@ public class EjbJar extends MatchingTask { } /** - * Setter used to store the value of flatDestDir. - * @param inValue a string, either 'true' or 'false'. + * Set the classpath to use when resolving classes for inclusion in the jar. + * + * @param classpath the classpath to use. + */ + public void setClasspath(Path classpath) { + this.classpath = classpath; + } + + /** + * Set the flat dest dir flag. + * + * This flag controls whether the destination jars are written out in the + * destination directory with the same hierarchal structure from which + * the deployment descriptors have been read. If this is set to true the + * generated EJB jars are written into the root of the destination directory, + * otherwise they are written out in the same relative position as the deployment + * descriptors in the descriptor directory. + * + * @param inValue the new value of the flatdestdir flag. */ public void setFlatdestdir(boolean inValue) { this.flatDestDir = inValue; } /** - * Setter used to store the suffix for the generated jar file. + * Set the suffix for the generated jar file. + * When generic jars are generated, they have a suffix which is appended to the + * the bean name to create the name of the jar file. Note that this suffix includes + * the extension fo te jar file and should therefore end with an appropriate + * extension such as .jar or .ear + * * @param inString the string to use as the suffix. */ public void setGenericjarsuffix(String inString) { @@ -195,31 +269,30 @@ public class EjbJar extends MatchingTask { } /** - * Setter used to store the value of baseNameTerminator + * Set the baseNameTerminator. + * + * The basename terminator is the string which terminates the bean name. The convention + * used by this task is that bean descriptors are named as the BeanName with some suffix. + * The baseNameTerminator string separates the bean name and the suffix and is used to + * determine the bean name. + * * @param inValue a string which marks the end of the basename. */ public void setBasenameterminator(String inValue) { this.baseNameTerminator = inValue; } - /** - * Setter used to store the value of generateweblogic. - * @param inValue a string, either 'true' or 'false'. - */ - public void setGenerateweblogic(String inValue) { - log("The syntax for using ejbjar with Weblogic has changed.", Project.MSG_ERR); - log("Please refer to the ejbjar documentation" + - " for information on the using the nested element", Project.MSG_ERR); - throw new BuildException("generateweblogic not supported - use nested element"); - } - /** * Invoked by Ant after the task is prepared, when it is ready to execute - * this task. Parses the XML deployment descriptor to acquire the list of - * files, then constructs the destination jar file (first deleting it if it - * already exists) from the list of classfiles encountered and the descriptor - * itself. File will be of the expected format with classes under full - * package hierarchies and the descriptor in META-INF/ejb-jar.xml + * this task. + * + * This will configure all of the nested deployment tools to allow them to + * process the jar. If no deployment tools have been configured a generic + * tool is created to handle the jar. + * + * A parser is configured and then each descriptor found is passed to all + * the deployment tool elements for processing. + * * @exception BuildException thrown whenever a problem is * encountered that cannot be recovered from, to signal to ant * that a major problem occurred within this task. @@ -234,7 +307,6 @@ public class EjbJar extends MatchingTask { genericTool.setDestdir(destDir); genericTool.setTask(this); genericTool.setGenericJarSuffix(genericJarSuffix); - deploymentTools.add(genericTool); } @@ -245,7 +317,7 @@ public class EjbJar extends MatchingTask { for (Iterator i = deploymentTools.iterator(); i.hasNext(); ) { EJBDeploymentTool tool = (EJBDeploymentTool)i.next(); - tool.configure(srcDir, scanDir, baseNameTerminator, baseJarName, flatDestDir); + tool.configure(srcDir, scanDir, baseNameTerminator, baseJarName, flatDestDir, classpath); tool.validateConfigured(); } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java index 0ea083a83..4d6a97477 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java @@ -58,15 +58,23 @@ import java.io.*; import java.util.*; import java.util.jar.*; import java.util.zip.*; +import java.net.*; import javax.xml.parsers.SAXParser; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.Project; -import org.apache.tools.ant.Task; +import org.apache.tools.ant.*; +import org.apache.tools.ant.types.*; +/** + * A deployment tool which creates generic EJB jars. Generic jars contains + * only those classes and META-INF entries specified in the EJB 1.1 standard + * + * This class is also used as a framework for the creation of vendor specific + * deployment tools. A number of template methods are provided through which the + * vendor specific tool can hook into the EJB creation process. + */ public class GenericDeploymentTool implements EJBDeploymentTool { /** Private constants that are used when constructing the standard jarfile */ protected static final String META_DIR = "META-INF/"; @@ -84,6 +92,9 @@ public class GenericDeploymentTool implements EJBDeploymentTool { /** Instance variable that stores the jar file name when not using the naming standard */ private String baseJarName; + /** The classpath to use with this deployment tool. */ + private Path classpath; + /** * Instance variable that determines whether to use a package structure * of a flat directory as the destination for the jar files. @@ -97,10 +108,22 @@ public class GenericDeploymentTool implements EJBDeploymentTool { private String genericJarSuffix = "-generic.jar"; /** - * The task to which this tool belongs. + * The task to which this tool belongs. This is used to access services provided + * by the ant core, such as logging. */ private Task task; + /** + * The classloader generated from the given classpath to load + * the super classes and super interfaces. + */ + private ClassLoader classpathLoader = null; + + /** + * List of files have been loaded into the EJB jar + */ + private List addedfiles; + /** * Setter used to store the value of destination directory prior to execute() * being called. @@ -176,16 +199,49 @@ public class GenericDeploymentTool implements EJBDeploymentTool { this.genericJarSuffix = inString; } + /** + * creates a nested classpath element. + */ + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(task.getProject()); + } + return classpath.createPath(); + } + + /** + * Set the classpath to be used for this compilation. + */ + public void setClasspath(Path classpath) { + this.classpath = classpath; + } + + protected Path getClasspath() { + return classpath; + } + + protected void log(String message, int level) { + getTask().log(message, level); + } + + /** * Configure this tool for use in the ejbjar task. */ public void configure(File srcDir, File descriptorDir, String baseNameTerminator, - String baseJarName, boolean flatDestDir) { + String baseJarName, boolean flatDestDir, Path classpath) { this.srcDir = srcDir; this.descriptorDir = descriptorDir; this.baseJarName = baseJarName; this.baseNameTerminator = baseNameTerminator; this.flatDestDir = flatDestDir; + if (this.classpath != null) { + this.classpath.append(classpath); + } + else { + this.classpath = classpath; + } + classpathLoader = null; } /** @@ -194,39 +250,52 @@ public class GenericDeploymentTool implements EJBDeploymentTool { * constructed. * @param jStream A JarOutputStream into which to write the * jar entry. - * @param iStream A FileInputStream from which to read the + * @param inputFile A File from which to read the * contents the file being added. - * @param filename A String representing the name, including + * @param logicalFilename A String representing the name, including * all relevant path information, that should be stored for the entry * being added. */ protected void addFileToJar(JarOutputStream jStream, - FileInputStream iStream, - String filename) + File inputFile, + String logicalFilename) throws BuildException { + FileInputStream iStream = null; try { - // Create the zip entry and add it to the jar file - ZipEntry zipEntry = new ZipEntry(filename); - jStream.putNextEntry(zipEntry); - - // Create the file input stream, and buffer everything over - // to the jar output stream - byte[] byteBuffer = new byte[2 * 1024]; - int count = 0; - do { - jStream.write(byteBuffer, 0, count); - count = iStream.read(byteBuffer, 0, byteBuffer.length); - } while (count != -1); - - // Close up the file input stream for the class file - iStream.close(); + if (!addedfiles.contains(logicalFilename)) { + iStream = new FileInputStream(inputFile); + // Create the zip entry and add it to the jar file + ZipEntry zipEntry = new ZipEntry(logicalFilename); + jStream.putNextEntry(zipEntry); + + // Create the file input stream, and buffer everything over + // to the jar output stream + byte[] byteBuffer = new byte[2 * 1024]; + int count = 0; + do { + jStream.write(byteBuffer, 0, count); + count = iStream.read(byteBuffer, 0, byteBuffer.length); + } while (count != -1); + + //add it to list of files in jar + addedfiles.add(logicalFilename); + } } catch (IOException ioe) { String msg = "IOException while adding entry " - + filename + "to jarfile." + + logicalFilename + " to jarfile from " + inputFile.getPath() + "." + ioe.getMessage(); throw new BuildException(msg, ioe); } + finally { + // Close up the file input stream for the class file + if (iStream != null) { + try { + iStream.close(); + } + catch (IOException closeException) {} + } + } } protected DescriptorHandler getDescriptorHandler(File srcDir) { @@ -234,6 +303,8 @@ public class GenericDeploymentTool implements EJBDeploymentTool { } public void processDescriptor(String descriptorFileName, SAXParser saxParser) { + FileInputStream descriptorStream = null; + try { DescriptorHandler handler = getDescriptorHandler(srcDir); @@ -241,10 +312,8 @@ public class GenericDeploymentTool implements EJBDeploymentTool { * look like much, we use a SAXParser and an inner class to * get hold of all the classfile names for the descriptor. */ - saxParser.parse(new InputSource - (new FileInputStream - (new File(getDescriptorDir(), descriptorFileName))), - handler); + descriptorStream = new FileInputStream(new File(getDescriptorDir(), descriptorFileName)); + saxParser.parse(new InputSource(descriptorStream), handler); Hashtable ejbFiles = handler.getFiles(); @@ -273,8 +342,12 @@ public class GenericDeploymentTool implements EJBDeploymentTool { // First the regular deployment descriptor ejbFiles.put(META_DIR + EJB_DD, new File(getDescriptorDir(), descriptorFileName)); - + + // now the vendor specific files, if any addVendorFiles(ejbFiles, baseName); + + // add any inherited files + checkAndAddInherited(ejbFiles); // Lastly create File object for the Jar files. If we are using // a flat destination dir, then we need to redefine baseName! @@ -304,6 +377,10 @@ public class GenericDeploymentTool implements EJBDeploymentTool { while( (needBuild == false) && (fileIter.hasNext()) ) { File currentFile = (File) fileIter.next(); needBuild = ( lastBuild < currentFile.lastModified() ); + if (needBuild) { + log("Build needed because " + currentFile.getPath() + " is out of date", + Project.MSG_VERBOSE); + } } } @@ -311,7 +388,7 @@ public class GenericDeploymentTool implements EJBDeploymentTool { // doing the work! if (needBuild) { // Log that we are going to build... - getTask().log( "building " + log( "building " + jarFile.getName() + " with " + String.valueOf(ejbFiles.size()) @@ -324,7 +401,7 @@ public class GenericDeploymentTool implements EJBDeploymentTool { } else { // Log that the file is up to date... - getTask().log(jarFile.toString() + " is up to date.", + log(jarFile.toString() + " is up to date.", Project.MSG_INFO); } @@ -345,6 +422,14 @@ public class GenericDeploymentTool implements EJBDeploymentTool { + ioe.getMessage(); throw new BuildException(msg, ioe); } + finally { + if (descriptorStream != null) { + try { + descriptorStream.close(); + } + catch (IOException closeException) {} + } + } } /** @@ -352,6 +437,7 @@ public class GenericDeploymentTool implements EJBDeploymentTool { * EJB Jar. */ protected void addVendorFiles(Hashtable ejbFiles, String baseName) { + // nothing to add for generic tool. } @@ -369,14 +455,12 @@ public class GenericDeploymentTool implements EJBDeploymentTool { * ejbFiles. */ protected void writeJar(String baseName, File jarfile, Hashtable files) throws BuildException{ - JarOutputStream jarStream = null; - Iterator entryIterator = null; - String entryName = null; - File entryFile = null; - File entryDir = null; - String innerfiles[] = null; + JarOutputStream jarStream = null; try { + // clean the addedfiles Vector + addedfiles = new ArrayList(); + /* If the jarfile already exists then whack it and recreate it. * Should probably think of a more elegant way to handle this * so that in case of errors we don't leave people worse off @@ -389,45 +473,39 @@ public class GenericDeploymentTool implements EJBDeploymentTool { jarfile.createNewFile(); // Create the streams necessary to write the jarfile + jarStream = new JarOutputStream(new FileOutputStream(jarfile)); jarStream.setMethod(JarOutputStream.DEFLATED); // Loop through all the class files found and add them to the jar - entryIterator = files.keySet().iterator(); - while (entryIterator.hasNext()) { - entryName = (String) entryIterator.next(); - entryFile = (File) files.get(entryName); + for (Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext(); ) { + String entryName = (String) entryIterator.next(); + File entryFile = (File) files.get(entryName); - getTask().log("adding file '" + entryName + "'", + log("adding file '" + entryName + "'", Project.MSG_VERBOSE); - addFileToJar(jarStream, - new FileInputStream(entryFile), - entryName); - - // See if there are any inner classes for this class and add them in if there are - InnerClassFilenameFilter flt = new InnerClassFilenameFilter(entryFile.getName()); - entryDir = entryFile.getParentFile(); - innerfiles = entryDir.list(flt); - for (int i=0, n=innerfiles.length; i < n; i++) { - - //get and clean up innerclass name - entryName = entryName.substring(0, entryName.lastIndexOf(entryFile.getName())-1) + File.separatorChar + innerfiles[i]; - - // link the file - entryFile = new File(srcDir, entryName); - - getTask().log("adding innerclass file '" + entryName + "'", - Project.MSG_VERBOSE); + addFileToJar(jarStream, entryFile, entryName); - addFileToJar(jarStream, - new FileInputStream(entryFile), - entryName); - - } + // See if there are any inner classes for this class and add them in if there are + InnerClassFilenameFilter flt = new InnerClassFilenameFilter(entryFile.getName()); + File entryDir = entryFile.getParentFile(); + String[] innerfiles = entryDir.list(flt); + for (int i=0, n=innerfiles.length; i < n; i++) { + + //get and clean up innerclass name + entryName = entryName.substring(0, entryName.lastIndexOf(entryFile.getName())-1) + File.separatorChar + innerfiles[i]; + + // link the file + entryFile = new File(srcDir, entryName); + + log("adding innerclass file '" + entryName + "'", + Project.MSG_VERBOSE); + + addFileToJar(jarStream, entryFile, entryName); + + } } - // All done. Close the jar stream. - jarStream.close(); } catch(IOException ioe) { String msg = "IOException while processing ejb-jar file '" @@ -436,12 +514,217 @@ public class GenericDeploymentTool implements EJBDeploymentTool { + ioe.getMessage(); throw new BuildException(msg, ioe); } + finally { + if (jarStream != null) { + try { + jarStream.close(); + } + catch (IOException closeException) {} + } + } } // end of writeJar + /** + * Check if a EJB Class Inherits from a Superclass, and if a Remote Interface + * extends an interface other then javax.ejb.EJBObject directly. Then add those + * classes to the generic-jar so they dont have to added elsewhere. + * + */ + protected void checkAndAddInherited(Hashtable checkEntries) throws BuildException + { + //Copy hashtable so were not changing the one we iterate through + Hashtable copiedHash = (Hashtable)checkEntries.clone(); + + // Walk base level EJBs and see if they have superclasses or extend extra interfaces which extend EJBObject + for (Iterator entryIterator = copiedHash.keySet().iterator(); entryIterator.hasNext(); ) + { + String entryName = (String)entryIterator.next(); + File entryFile = (File)copiedHash.get(entryName); + + // only want class files, xml doesnt reflect very well =) + if (entryName.endsWith(".class")) + { + String classname = entryName.substring(0,entryName.lastIndexOf(".class")).replace(File.separatorChar,'.'); + ClassLoader loader = getClassLoaderForBuild(); + try { + Class c = loader.loadClass(classname); + + // No primatives!! sanity check, probably not nessesary + if (!c.isPrimitive()) + { + if (c.isInterface()) //get as an interface + { + log("looking at interface " + c.getName(), Project.MSG_VERBOSE); + Class[] interfaces = c.getInterfaces(); + for (int i = 0; i < interfaces.length; i++){ + + log(" implements " + interfaces[i].getName(), Project.MSG_VERBOSE); + if (!interfaces[i].getName().equals("javax.ejb.EJBObject")) // do not add home interfaces + { + File superClassFile = new File(srcDir.getAbsolutePath() + + File.separatorChar + + interfaces[i].getName().replace('.',File.separatorChar) + + ".class" + ); + if (superClassFile.exists() && superClassFile.isFile()) + { + if (checkInterfaceClasses(interfaces[i].getName().replace('.',File.separatorChar)+".class", + superClassFile, checkEntries)) + { + checkEntries.put(interfaces[i].getName().replace('.',File.separatorChar)+".class", + superClassFile); + } + } + } + } + } + else // get as a class + { + log("looking at class " + c.getName(), Project.MSG_VERBOSE); + Class s = c.getSuperclass(); + if (!s.getName().equals("java.lang.Object")) + { + File superClassFile = new File(srcDir.getAbsolutePath() + + File.separatorChar + + s.getName().replace('.',File.separatorChar) + + ".class" + ); + if (superClassFile.exists() && superClassFile.isFile()) + { + checkSuperClasses(s.getName().replace('.',File.separatorChar) + ".class", + superClassFile, checkEntries); + checkEntries.put(s.getName().replace('.',File.separatorChar) + ".class", + superClassFile); + } + } + } + } //if primative + } + catch (ClassNotFoundException cnfe) { + log("Could not load class " + classname + " for super class check", + Project.MSG_WARN); + } + } //if + } // while + } + + /** + * Returns a Classloader object which parses the passed in generic EjbJar classpath. + * The loader is used to dynamically load classes from javax.ejb.* and the classes + * being added to the jar. + * + */ + protected ClassLoader getClassLoaderForBuild() + { + if (classpathLoader != null) { + return classpathLoader; + } + + // only generate a URLClassLoader if we have a classpath + if (classpath == null) { + classpathLoader = getClass().getClassLoader(); + } + else { + classpathLoader = new AntClassLoader(getTask().getProject(), classpath); + } + + return classpathLoader; + } + /** + * Checks to see if a Superclass of an Object needs to be included in the EJB Jar. + * This is done my checking the class and if it inherits from a superclass and that + * superclass is available then it includes that in the Hashtable of entries to be added + * to the Jar. + * + */ + protected void checkSuperClasses(String entryName, File entryFile, Hashtable checkEntries) + { + try + { + if (entryName.endsWith(".class")) //sanity check + { + // Load class to check superclass and interfaces + ClassLoader loader = getClassLoaderForBuild(); + String classname = entryName.substring(0,entryName.lastIndexOf(".class")).replace(File.separatorChar,'.'); + Class c = loader.loadClass(classname); + + Class s = c.getSuperclass(); + if (!s.getName().equals("java.lang.Object")) + { + File superClassFile = new File(srcDir.getAbsolutePath() + + File.separatorChar + + s.getName().replace('.',File.separatorChar) + + ".class" + ); + if (superClassFile.exists() && superClassFile.isFile()){ + checkSuperClasses(s.getName().replace('.',File.separatorChar) + ".class", superClassFile, checkEntries); + checkEntries.put(s.getName().replace('.',File.separatorChar) + ".class", superClassFile); + } + } + } + } + catch(ClassNotFoundException cnfe){ + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + ". Details: " + + cnfe.getMessage(); + throw new BuildException(cnfmsg, cnfe); + } + } + /** + * Checks to see if an interface extends another interface and if the final interface on the + * chain implements javax.ejb.EJBObject the it includes all interfaces in that chain in the Jar. + * + */ + protected boolean checkInterfaceClasses(String entryName, File entryFile, Hashtable checkEntries) + { + boolean addit = false; + try + { + if (entryName.endsWith(".class")) //sanity check + { + // Load class to check superclass and interfaces + ClassLoader loader = getClassLoaderForBuild(); + String classname = entryName.substring(0,entryName.lastIndexOf(".class")).replace(File.separatorChar,'.'); + Class c = loader.loadClass(classname); + + Class[] interfaces = c.getInterfaces(); + for (int i = 0; i < interfaces.length; i++){ + if (!interfaces[i].getName().equals("javax.ejb.EJBObject")){ // do not add home interfaces + File superClassFile = new File(srcDir.getAbsolutePath() + + File.separatorChar + + interfaces[i].getName().replace('.',File.separatorChar) + + ".class" + ); + if (superClassFile.exists() && superClassFile.isFile()){ + log("looking at interface " + interfaces[i].getName(), Project.MSG_VERBOSE); + + addit = checkInterfaceClasses(interfaces[i].getName().replace('.',File.separatorChar)+".class", + superClassFile, checkEntries); + if (addit) + { + log("adding at interface " + interfaces[i].getName(), Project.MSG_VERBOSE); + checkEntries.put(interfaces[i].getName().replace('.',File.separatorChar)+".class", + superClassFile); + } + } + } + else { + addit = true; + } + } + } + } + catch(ClassNotFoundException cnfe){ + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + ". Details: " + + cnfe.getMessage(); + throw new BuildException(cnfmsg, cnfe); + } + return addit; + } - /** * Called to validate that the tool parameters have been configured. * diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java index e336b386e..951058929 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java @@ -55,7 +55,9 @@ package org.apache.tools.ant.taskdefs.optional.ejb; import java.io.*; +import java.util.jar.*; import java.util.*; +import java.net.*; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; @@ -76,8 +78,6 @@ public class WeblogicDeploymentTool extends GenericDeploymentTool { /** Instance variable that stores the location of the weblogic DTD file. */ private String weblogicDTD; - private Path classpath; - /** Instance variable that determines whether generic ejb jars are kept. */ private boolean keepgenerated = false; @@ -87,20 +87,22 @@ public class WeblogicDeploymentTool extends GenericDeploymentTool { private boolean keepGeneric = false; private String compiler = null; + + private boolean alwaysRebuild = true; /** - * Set the classpath to be used for this compilation. + * The compiler (switch -compiler) to use */ - public void setClasspath(Path classpath) { - this.classpath = classpath; + public void setCompiler(String compiler) { + this.compiler = compiler; } - + /** - * The compiler (switch -compiler) to use + * Set the rebuild flag to false to only update changes in the + * jar rather than rerunning ejbc */ - public void setCompiler(String compiler) - { - this.compiler = compiler; + public void setRebuild(boolean rebuild) { + this.alwaysRebuild = rebuild; } @@ -226,6 +228,7 @@ public class WeblogicDeploymentTool extends GenericDeploymentTool { javaTask.setClassname("weblogic.ejbc"); Commandline.Argument arguments = javaTask.createArg(); arguments.setLine(args); + Path classpath = getClasspath(); if (classpath != null) { javaTask.setClasspath(classpath); javaTask.setFork(true); @@ -235,7 +238,7 @@ public class WeblogicDeploymentTool extends GenericDeploymentTool { } - getTask().log("Calling weblogic.ejbc for " + sourceJar.toString(), + log("Calling weblogic.ejbc for " + sourceJar.toString(), Project.MSG_VERBOSE); javaTask.execute(); @@ -257,9 +260,12 @@ public class WeblogicDeploymentTool extends GenericDeploymentTool { File genericJarFile = super.getVendorOutputJarFile(baseName); super.writeJar(baseName, genericJarFile, files); - buildWeblogicJar(genericJarFile, jarFile); + if (alwaysRebuild || isRebuildRequired(genericJarFile, jarFile)) + { + buildWeblogicJar(genericJarFile, jarFile); + } if (!keepGeneric) { - getTask().log("deleting generic jar " + genericJarFile.toString(), + log("deleting generic jar " + genericJarFile.toString(), Project.MSG_VERBOSE); genericJarFile.delete(); } @@ -272,4 +278,222 @@ public class WeblogicDeploymentTool extends GenericDeploymentTool { public void validateConfigured() throws BuildException { super.validateConfigured(); } + + + /** + * Helper method to check to see if a weblogic EBJ1.1 jar needs to be rebuilt using + * ejbc. Called from writeJar it sees if the "Bean" classes are the only thing that needs + * to be updated and either updates the Jar with the Bean classfile or returns true, + * saying that the whole weblogic jar needs to be regened with ejbc. This allows faster + * build times for working developers. + *

+ * The way weblogic ejbc works is it creates wrappers for the publicly defined methods as + * they are exposed in the remote interface. If the actual bean changes without changing the + * the method signatures then only the bean classfile needs to be updated and the rest of the + * weblogic jar file can remain the same. If the Interfaces, ie. the method signatures change + * or if the xml deployment dicriptors changed, the whole jar needs to be rebuilt with ejbc. + * This is not strictly true for the xml files. If the JNDI name changes then the jar doesnt + * have to be rebuild, but if the resources references change then it does. At this point the + * weblogic jar gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param weblogicJarFile java.io.File The weblogic jar file to check to see if it needs to be rebuilt. + */ + protected boolean isRebuildRequired(File genericJarFile, File weblogicJarFile) + { + boolean rebuild = false; + + JarFile genericJar = null; + JarFile wlJar = null; + File newWLJarFile = null; + JarOutputStream newJarStream = null; + + try + { + log("Checking if weblogic Jar needs to be rebuilt for jar " + weblogicJarFile.getName(), + Project.MSG_VERBOSE); + // Only go forward if the generic and the weblogic file both exist + if (genericJarFile.exists() && genericJarFile.isFile() + && weblogicJarFile.exists() && weblogicJarFile.isFile()) + { + //open jar files + genericJar = new JarFile(genericJarFile); + wlJar = new JarFile(weblogicJarFile); + + Hashtable genericEntries = new Hashtable(); + Hashtable wlEntries = new Hashtable(); + Hashtable replaceEntries = new Hashtable(); + + //get the list of generic jar entries + for (Enumeration e = genericJar.entries(); e.hasMoreElements();) + { + JarEntry je = (JarEntry)e.nextElement(); + genericEntries.put(je.getName().replace('\\', '/'), je); + } + //get the list of weblogic jar entries + for (Enumeration e = wlJar.entries() ; e.hasMoreElements();) + { + JarEntry je = (JarEntry)e.nextElement(); + wlEntries.put(je.getName(), je); + } + + //Cycle Through generic and make sure its in weblogic + ClassLoader genericLoader = getClassLoaderFromJar(genericJarFile); + for (Enumeration e = genericEntries.keys(); e.hasMoreElements();) + { + String filepath = (String)e.nextElement(); + if (wlEntries.containsKey(filepath)) // File name/path match + { + // Check files see if same + JarEntry genericEntry = (JarEntry)genericEntries.get(filepath); + JarEntry wlEntry = (JarEntry)wlEntries.get(filepath); + if ((genericEntry.getCrc() != wlEntry.getCrc()) || // Crc's Match + (genericEntry.getSize() != wlEntry.getSize()) ) // Size Match + { + if (genericEntry.getName().endsWith(".class")) + { + //File are different see if its an object or an interface + String classname = genericEntry.getName().replace(File.separatorChar,'.'); + classname = classname.substring(0,classname.lastIndexOf(".class")); + Class genclass = genericLoader.loadClass(classname); + if (genclass.isInterface()) + { + //Interface changed rebuild jar. + log("Interface " + genclass.getName() + " has changed",Project.MSG_VERBOSE); + rebuild = true; + break; + } + else + { + //Object class Changed update it. + replaceEntries.put(filepath, genericEntry); + } + } + else + { + //File other then class changed rebuild + log("Non class file " + genericEntry.getName() + " has changed",Project.MSG_VERBOSE); + rebuild = true; + break; + } + } + } + else // a file doesnt exist rebuild + { + log("File " + filepath + " not present in weblogic jar",Project.MSG_VERBOSE); + rebuild = true; + break; + } + } + + if (!rebuild) + { + log("No rebuild needed - updating jar",Project.MSG_VERBOSE); + newWLJarFile = new File(weblogicJarFile.getAbsolutePath() + ".temp"); + if (newWLJarFile.exists()) { + newWLJarFile.delete(); + } + + newJarStream = new JarOutputStream(new FileOutputStream(newWLJarFile)); + + //Copy files from old weblogic jar + for (Enumeration e = wlEntries.elements() ; e.hasMoreElements();) + { + byte[] buffer = new byte[1024]; + int bytesRead; + InputStream is; + JarEntry je = (JarEntry)e.nextElement(); + + // Update with changed Bean class + if (replaceEntries.containsKey(je.getName())) + { + log("Updating Bean class from generic Jar " + je.getName(),Project.MSG_VERBOSE); + // Use the entry from the generic jar + je = (JarEntry)replaceEntries.get(je.getName()); + is = genericJar.getInputStream(je); + } + else //use fle from original weblogic jar + { + is = wlJar.getInputStream(je); + } + newJarStream.putNextEntry(new JarEntry(je.getName())); + + while ((bytesRead = is.read(buffer)) != -1) + { + newJarStream.write(buffer,0,bytesRead); + } + is.close(); + } + } + else + { + log("Weblogic Jar rebuild needed due to changed interface or XML",Project.MSG_VERBOSE); + } + } + else + { + rebuild = true; + } + } + catch(ClassNotFoundException cnfe) + { + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + ". Details: " + + cnfe.getMessage(); + throw new BuildException(cnfmsg, cnfe); + } + catch(IOException ioe) { + String msg = "IOException while processing ejb-jar file " + + ". Details: " + + ioe.getMessage(); + throw new BuildException(msg, ioe); + } + finally { + // need to close files and perhaps rename output + if (genericJar != null) { + try { + genericJar.close(); + } + catch (IOException closeException) {} + } + + if (wlJar != null) { + try { + wlJar.close(); + } + catch (IOException closeException) {} + } + + if (newJarStream != null) { + try { + newJarStream.close(); + } + catch (IOException closeException) {} + + weblogicJarFile.delete(); + newWLJarFile.renameTo(weblogicJarFile); + if (!weblogicJarFile.exists()) { + rebuild = true; + } + } + } + + return rebuild; + } + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for + * a Jar File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + */ + protected ClassLoader getClassLoaderFromJar(File classjar) throws IOException + { + URLClassLoader loader; + URL[] aURL = new URL[1]; + + aURL[0] = new URL("file","",0,classjar.getAbsolutePath()); + loader = new URLClassLoader(aURL); + return loader; + } }