@@ -1,7 +1,7 @@
/*
/*
* The Apache Software License, Version 1.1
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
* reserved.
*
*
* Redistribution and use in source and binary forms, with or without
* Redistribution and use in source and binary forms, with or without
@@ -55,37 +55,20 @@
package org.apache.tools.ant.taskdefs.optional.ejb;
package org.apache.tools.ant.taskdefs.optional.ejb;
// Standard java imports
// Standard java imports
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.File;
import java.io.IOException;
import java.util.jar.*;
import java.util.zip.*;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.io.*;
import java.util.*;
// XML imports
// XML imports
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.Parser;
import org.xml.sax.Locator;
import org.xml.sax.InputSource;
import org.xml.sax.AttributeList;
import org.xml.sax.DocumentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.ParserFactory;
// Apache/Ant imports
// Apache/Ant imports
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.taskdefs.Java;
/**
/**
* <p>Provides automated ejb jar file creation for ant. Extends the MatchingTask
* <p>Provides automated ejb jar file creation for ant. Extends the MatchingTask
@@ -104,146 +87,21 @@ import org.apache.tools.ant.taskdefs.Java;
* 5.1 jars. The weblogic deployment descriptors, used in constructing the
* 5.1 jars. The weblogic deployment descriptors, used in constructing the
* Weblogic jar, are located based on a simple naming convention. The name of the
* Weblogic jar, are located based on a simple naming convention. The name of the
* standard deployment descriptor is taken upto the first instance of a String,
* standard deployment descriptor is taken upto the first instance of a String,
* specified by the attribute basenamet erminator, and then the regular Weblogic
* descriptor name is appended. For example if basenamet erminator is set to '-',
* specified by the attribute basenameT erminator, and then the regular Weblogic
* descriptor name is appended. For example if basenameT erminator is set to '-',
* its default value, and a standard descriptor is called Foo-ejb-jar.xml then
* its default value, and a standard descriptor is called Foo-ejb-jar.xml then
* the files Foo-weblogic-ejb-jar.xml and Foo-weblogic-cmp-rdbms-jar.xml will be
* the files Foo-weblogic-ejb-jar.xml and Foo-weblogic-cmp-rdbms-jar.xml will be
* looked for, and if found, included in the jarfile.</p>
* looked for, and if found, included in the jarfile.</p>
*
*
* <p>Attributes and setter methods are provided to support optional generation
* <p>Attributes and setter methods are provided to support optional generation
* of Weblogic5.1 jars, optional deletion of generic jar files, setting alternate
* of Weblogic5.1 jars, optional deletion of generic jar files, setting alternate
* values for basenamet erminator, and setting the strings to append to the names
* values for basenameT erminator, and setting the strings to append to the names
* of the generated jarfiles.</p>
* of the generated jarfiles.</p>
*
*
* @author <a href="mailto:tfennell@sapient.com">Tim Fennell</a>
* @author <a href="mailto:tfennell@sapient.com">Tim Fennell</a>
*/
*/
public class EjbJar extends MatchingTask {
public class EjbJar extends MatchingTask {
/**
* Inner class used by EjbJar to facilitate the parsing of deployment
* descriptors and the capture of appropriate information. Extends
* HandlerBase so it only implements the methods needed. During parsing
* creates a hashtable consisting of entries mapping the name it should be
* inserted into an EJB jar as to a File representing the file on disk. This
* list can then be accessed through the getFiles() method.
*/
protected class DescriptorHandler extends org.xml.sax.HandlerBase {
/**
* Bunch of constants used for storing entries in a hashtable, and for
* constructing the filenames of various parts of the ejb jar.
*/
private static final String HOME_INTERFACE = "home";
private static final String REMOTE_INTERFACE = "remote";
private static final String BEAN_CLASS = "ejb-class";
private static final String PK_CLASS = "prim-key-class";
/**
* Instance variable used to store the name of the current attribute being
* processed by the SAX parser. Accessed by the SAX parser call-back methods
* startElement() and endElement().
*/
private String currentAttribute = null;
/**
* Instance variable that stores the names of the files as they will be
* put into the jar file, mapped to File objects Accessed by the SAX
* parser call-back method characters().
*/
private Hashtable ejbFiles = null;
/** Instance variable to store the source directory of the task */
/**
* Getter method that returns the set of files to include in the EJB jar.
*/
public Hashtable getFiles() {
return (ejbFiles == null) ? new Hashtable() : ejbFiles;
}
/**
* SAX parser call-back method that is used to initialize the values of some
* instance variables to ensure safe operation.
*/
public void startDocument() throws SAXException {
this.ejbFiles = new Hashtable(10, 1);
this.currentAttribute = null;
}
/**
* SAX parser call-back method that is invoked when a new element is entered
* into. Used to store the context (attribute name) in the currentAttribute
* instance variable.
* @param name The name of the element being entered.
* @param attrs Attributes associated to the element.
*/
public void startElement(String name, AttributeList attrs)
throws SAXException {
this.currentAttribute = name;
}
/**
* SAX parser call-back method that is invoked when an element is exited.
* Used to blank out (set to the empty string, not nullify) the name of
* the currentAttribute. A better method would be to use a stack as an
* instance variable, however since we are only interested in leaf-node
* data this is a simpler and workable solution.
* @param name The name of the attribute being exited. Ignored
* in this implementation.
*/
public void endElement(String name) throws SAXException {
this.currentAttribute = "";
}
/**
* SAX parser call-back method invoked whenever characters are located within
* an element. currentAttribute (modified by startElement and endElement)
* tells us whether we are in an interesting element (one of the up to four
* classes of an EJB). If so then converts the classname from the format
* org.apache.tools.ant.Parser to the convention for storing such a class,
* org/apache/tools/ant/Parser.class. This is then resolved into a file
* object under the srcdir which is stored in a Hashtable.
* @param ch A character array containing all the characters in
* the element, and maybe others that should be ignored.
* @param start An integer marking the position in the char
* array to start reading from.
* @param length An integer representing an offset into the
* char array where the current data terminates.
*/
public void characters(char[] ch, int start, int length)
throws SAXException {
if (currentAttribute.equals(DescriptorHandler.HOME_INTERFACE) ||
currentAttribute.equals(DescriptorHandler.REMOTE_INTERFACE) ||
currentAttribute.equals(DescriptorHandler.BEAN_CLASS) ||
currentAttribute.equals(DescriptorHandler.PK_CLASS)) {
// Get the filename into a String object
File classFile = null;
String className = new String(ch, start, length);
// If it's a primitive wrapper then we shouldn't try and put
// it into the jar, so ignore it.
if (!className.startsWith("java.lang")) {
// Translate periods into path separators, add .class to the
// name, create the File object and add it to the Hashtable.
className = className.replace('.', File.separatorChar);
className += ".class";
classFile = new File(srcdir, className);
ejbFiles.put(className, classFile);
}
}
}
} // End of DescriptorHandler
/** Private constants that are used when constructing the standard jarfile */
private static final String META_DIR = "META-INF/";
private static final String EJB_DD = "ejb-jar.xml";
private static final String WL_DD = "weblogic-ejb-jar.xml";
private static final String WL_CMP_DD = "weblogic-cmp-rdbms-jar.xml";
/** Stores a handle to the directory under which to search for files */
/** Stores a handle to the directory under which to search for files */
private File srcdir = null;
private File srcdir = null;
@@ -255,47 +113,50 @@ public class EjbJar extends MatchingTask {
* of a flat directory as the destination for the jar files.
* of a flat directory as the destination for the jar files.
*/
*/
private boolean flatdestdir = false;
private boolean flatdestdir = false;
/** Instance variable that determines whether to generate weblogic jars. */
private boolean generateweblogic = false;
/** Instance variable that determines whether generic ejb jars are kept. */
private boolean keepgeneric = true;
/** Instance variable that marks the end of the 'basename' */
/** Instance variable that marks the end of the 'basename' */
private String basenamet erminator = "-";
private String basenameTerminator = "-";
/** Instance variable that stores the suffix for the generated jarfile. */
/** Instance variable that stores the suffix for the generated jarfile. */
private String genericjarsuffix = "-generic.jar";
private String genericjarsuffix = "-generic.jar";
/** Instance variable that stores the suffix for the weblogic jarfile. */
private String weblogicjarsuffix = "-wl.jar";
/**
* The list of deployment tools we are going to run.
*/
private ArrayList deploymentTools = new ArrayList();
public EJBDeploymentTool createWeblogic() {
EJBDeploymentTool tool = new WeblogicDeploymentTool();
tool.setTask(this);
deploymentTools.add(tool);
return tool;
}
/**
/**
* Setter used to store the value of srcdir prior to execute() being called.
* Setter used to store the value of srcdir prior to execute() being called.
* @param inDir The string indicating the source directory.
* @param inDir the source directory.
*/
*/
public void setSrcdir(String inDir) {
this.srcdir = this.project.resolveFile(inDir);
public void setSrcdir(File inDir) {
this.srcdir = inDir;
}
}
/**
/**
* Setter used to store the value of destination directory prior to execute()
* Setter used to store the value of destination directory prior to execute()
* being called.
* being called.
* @param inFile The string indicating the source directory.
* @param inFile the destination directory.
*/
*/
public void setDestdir(String inDir) {
this.destdir = this.project.resolveFile( inDir) ;
public void setDestdir(File inDir) {
this.destdir = inDir;
}
}
/**
/**
* Setter used to store the value of flatdestdir.
* Setter used to store the value of flatdestdir.
* @param inValue a string, either 'true' or 'false'.
* @param inValue a string, either 'true' or 'false'.
*/
*/
public void setFlatdestdir(String inValue) {
this.flatdestdir = Boolean.valueOf( inValue).booleanValue() ;
public void setFlatdestdir(boolean inValue) {
this.flatdestdir = inValue;
}
}
/**
/**
* Setter used to store the suffix for the generated jar file.
* Setter used to store the suffix for the generated jar file.
* @param inString the string to use as the suffix.
* @param inString the string to use as the suffix.
@@ -305,164 +166,13 @@ public class EjbJar extends MatchingTask {
}
}
/**
/**
* Setter used to store the suffix for the generated weblogic jar file.
* @param inString the string to use as the suffix.
*/
public void setWeblogicjarsuffix(String inString) {
this.weblogicjarsuffix = inString;
}
/**
* Setter used to store the value of generateweblogic.
* @param inValue a string, either 'true' or 'false'.
*/
public void setGenerateweblogic(String inValue) {
this.generateweblogic = Boolean.valueOf(inValue).booleanValue();
}
/**
* Setter used to store the value of keepgeneric
* @param inValue a string, either 'true' or 'false'.
*/
public void setKeepgeneric(String inValue) {
this.keepgeneric = Boolean.valueOf(inValue).booleanValue();
}
/**
* Setter used to store the value of basenameterminator
* Setter used to store the value of basenameTerminator
* @param inValue a string which marks the end of the basename.
* @param inValue a string which marks the end of the basename.
*/
*/
public void setBasenameterminator(String inValue) {
if (inValue != null) this.basenameterminator = inValue;
}
/**
* Utility method that encapsulates the logic of adding a file entry to
* a .jar file. Used by execute() to add entries to the jar file as it is
* constructed.
* @param jStream A JarOutputStream into which to write the
* jar entry.
* @param iStream A FileInputStream from which to read the
* contents the file being added.
* @param filename 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)
throws BuildException {
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();
}
catch (IOException ioe) {
String msg = "IOException while adding entry "
+ filename + "to jarfile."
+ ioe.getMessage();
throw new BuildException(msg, ioe);
}
}
/**
* Method used to encapsulate the writing of the JAR file. Iterates over the
* filenames/java.io.Files in the Hashtable stored on the instance variable
* ejbFiles.
*/
public void writeJar(File jarfile, Hashtable files) throws BuildException{
JarOutputStream jarStream = null;
Iterator entryIterator = null;
String entryName = null;
File entryFile = null;
try {
/* 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
* than when we started =)
*/
if (jarfile.exists()) jarfile.delete();
jarfile.getParentFile().mkdirs();
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);
this.log("adding file '" + entryName + "'",
Project.MSG_VERBOSE);
addFileToJar(jarStream,
new FileInputStream(entryFile),
entryName);
}
// All done. Close the jar stream.
jarStream.close();
}
catch(IOException ioe) {
String msg = "IOException while processing ejb-jar file '"
+ jarfile.toString()
+ "'. Details: "
+ ioe.getMessage();
throw new BuildException(msg, ioe);
}
} // end of writeJar
/**
* Helper method invoked by execute() for each WebLogic jar to be built.
* Encapsulates the logic of constructing a java task for calling
* weblogic.ejbc and executing it.
* @param sourceJar java.io.File representing the source (EJB1.1) jarfile.
* @param destJar java.io.File representing the destination, WebLogic
* jarfile.
*/
public void buildWeblogicJar(File sourceJar, File destJar) {
org.apache.tools.ant.taskdefs.Java javaTask = null;
try {
// Unfortunately, because weblogic.ejbc calls system.exit(), we
// cannot do it 'in-process'. If they ever fix this, we should
// change this code - it would be much quicker!
String args = "-noexit " + sourceJar + " " + destJar;
javaTask = (Java) this.project.createTask("java");
javaTask.setClassname("weblogic.ejbc");
javaTask.setArgs(args);
javaTask.setFork(false);
this.log("Calling weblogic.ejbc for " + sourceJar.toString(),
Project.MSG_INFO);
javaTask.execute();
}
catch (Exception e) {
// Have to catch this because of the semantics of calling main()
String msg = "Exception while calling ejbc. Details: " + e.toString();
throw new BuildException(msg, e);
}
public void setBasenameTerminator(String inValue) {
if (inValue != null) this.basenameTerminator = inValue;
}
}
/**
/**
* Invoked by Ant after the task is prepared, when it is ready to execute
* 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
* this task. Parses the XML deployment descriptor to acquire the list of
@@ -475,173 +185,51 @@ public class EjbJar extends MatchingTask {
* that a major problem occurred within this task.
* that a major problem occurred within this task.
*/
*/
public void execute() throws BuildException {
public void execute() throws BuildException {
boolean needBuild = true;
DirectoryScanner ds = null;
String[] files = null;
int index = 0;
File weblogicDD = null;
File jarfile = null;
File wlJarfile = null;
File jarToCheck = null;
DescriptorHandler handler = null;
Hashtable ejbFiles = null;
String baseName = null;
// Lets do a little asserting to make sure we have all the
// required attributes from the task processor
StringBuffer sb = new StringBuffer();
boolean die = false;
sb.append("Processing ejbjar - the following attributes ");
sb.append("must be specified: ");
if (this.srcdir == null) { sb.append("srcdir "); die = true; }
if (this.destdir == null) { sb.append("destdir"); die = true; }
if ( die ) throw new BuildException(sb.toString());
if (srcdir == null) {
throw new BuildException("The srcdir attribute must be specified");
}
if (deploymentTools.size() == 0) {
GenericDeploymentTool genericTool = new GenericDeploymentTool();
genericTool.setDestdir(destdir);
genericTool.setTask(this);
genericTool.setGenericjarsuffix(genericjarsuffix);
deploymentTools.add(genericTool);
}
for (Iterator i = deploymentTools.iterator(); i.hasNext(); ) {
EJBDeploymentTool tool = (EJBDeploymentTool)i.next();
tool.configure(basenameTerminator, flatdestdir);
tool.validateConfigured();
}
try {
try {
// Create the parser using whatever parser the system dictates
// Create the parser using whatever parser the system dictates
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setValidating(false);
saxParserFactory.setValidating(tru e);
SAXParser saxParser = saxParserFactory.newSAXParser();
SAXParser saxParser = saxParserFactory.newSAXParser();
ds = this.getDirectoryScanner(this.srcdir);
DirectoryScanner ds = getDirectoryScanner(srcdir);
ds.scan();
ds.scan();
files = ds.getIncludedFiles();
this.log(files.length + " deployment descriptors located.",
Project.MSG_VERBOSE);
String[] files = ds.getIncludedFiles();
log(files.length + " deployment descriptors located.",
Project.MSG_VERBOSE);
// Loop through the files. Each file represents one deployment
// Loop through the files. Each file represents one deployment
// descriptor, and hence one bean in our model.
// descriptor, and hence one bean in our model.
for (index=0; index < files.length; ++index) {
// By default we assume we need to build.
needBuild = true;
// Work out what the base name is
int endBaseName =
files[index].indexOf(basenameterminator,
files[index].lastIndexOf(File.separator));
baseName = files[index].substring(0, endBaseName);
/* Parse the ejb deployment descriptor. While it may not
* look like much, we use a SAXParser and an inner class to
* get hold of all the classfile names for the descriptor.
*/
handler = new DescriptorHandler();
saxParser.parse(new InputSource
(new FileInputStream
(new File(this.srcdir, files[index]))),
handler);
ejbFiles = handler.getFiles();
/* Now try to locate all of the deployment descriptors for the
* jar, and if they exist, add them to the list of files.
*/
// First the regular deployment descriptor
ejbFiles.put(EjbJar.META_DIR + EjbJar.EJB_DD,
new File(this.srcdir, files[index]));
// Then the weblogic deployment descriptor
weblogicDD = new File(this.srcdir,
baseName
+ this.basenameterminator
+ EjbJar.WL_DD);
if (weblogicDD.exists()) {
ejbFiles.put(EjbJar.META_DIR + EjbJar.WL_DD,
weblogicDD);
for (int index = 0; index < files.length; ++index) {
// process the deployment descriptor in each tool
for (Iterator i = deploymentTools.iterator(); i.hasNext(); ) {
EJBDeploymentTool tool = (EJBDeploymentTool)i.next();
processDescriptor(files[index], saxParser, tool);
}
}
// The the weblogic cmp deployment descriptor
weblogicDD = new File(this.srcdir,
baseName
+ this.basenameterminator
+ EjbJar.WL_CMP_DD);
if (weblogicDD.exists()) {
ejbFiles.put(EjbJar.META_DIR + EjbJar.WL_CMP_DD,
weblogicDD);
}
// Lastly create File object for the Jar files. If we are using
// a flat destination dir, then we need to redefine baseName!
if (this.flatdestdir) {
int startName = baseName.lastIndexOf(File.separator);
int endName = baseName.length();
baseName = baseName.substring(startName, endName);
}
jarfile = new File(this.destdir,
baseName
+ this.genericjarsuffix);
wlJarfile = new File(this.destdir,
baseName
+ this.weblogicjarsuffix);
/* Check to see if the jar file is already up to date.
* Unfortunately we have to parse the descriptor just to do
* that, but it's still a saving over re-constructing the jar
* file each time. Tertiary is used to determine which jarfile
* we should check times against...think about it.
*/
jarToCheck = this.generateweblogic ? wlJarfile : jarfile;
if (jarToCheck.exists()) {
long lastBuild = jarToCheck.lastModified();
Iterator fileIter = ejbFiles.values().iterator();
File currentFile = null;
// Set the need build to false until we find out otherwise.
needBuild = false;
// Loop through the files seeing if any has been touched
// more recently than the destination jar.
while( (needBuild == false) && (fileIter.hasNext()) ) {
currentFile = (File) fileIter.next();
needBuild = ( lastBuild < currentFile.lastModified() );
}
}
// Check to see if we need a build and start
// doing the work!
if (needBuild) {
// Log that we are going to build...
this.log( "building "
+ jarfile.getName()
+ " with "
+ String.valueOf(ejbFiles.size())
+ " total files",
Project.MSG_INFO);
// Use helper method to write the jarfile
this.writeJar(jarfile, ejbFiles);
// Generate weblogic jar if requested
if (this.generateweblogic) {
this.buildWeblogicJar(jarfile, wlJarfile);
}
// Delete the original jar if we weren't asked to keep it.
if (!this.keepgeneric) {
this.log("deleting jar " + jarfile.toString(),
Project.MSG_INFO);
jarfile.delete();
}
}
else {
// Log that the file is up to date...
this.log(jarfile.toString() + " is up to date.",
Project.MSG_INFO);
}
}
}
}
}
catch (SAXException se) {
catch (SAXException se) {
String msg = "SAXException while parsing '"
+ files[index].toString()
+ "'. This probably indicates badly-formed XML."
String msg = "SAXException while creating parser."
+ " Details: "
+ " Details: "
+ se.getMessage();
+ se.getMessage();
throw new BuildException(msg, se);
throw new BuildException(msg, se);
@@ -651,15 +239,14 @@ public class EjbJar extends MatchingTask {
+ "Details: " + pce.getMessage();
+ "Details: " + pce.getMessage();
throw new BuildException(msg, pce);
throw new BuildException(msg, pce);
}
}
catch (IOException ioe) {
String msg = "IOException while parsing'"
+ files[index].toString()
+ "'. This probably indicates that the descriptor"
+ " doesn't exist. Details:"
+ ioe.getMessage();
throw new BuildException(msg, ioe);
}
} // end of execute()
} // end of execute()
private void processDescriptor(String descriptorFilename, SAXParser saxParser,
EJBDeploymentTool tool) {
tool.processDescriptor(srcdir, descriptorFilename, saxParser);
}
}
}