blob: ebbeb5716fe6491e72fa9dbdc2d290f1be42b424 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.ejb;
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.*;
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/";
protected static final String EJB_DD = "ejb-jar.xml";
/**
* The configuration from the containing task. This config combined with the
* settings of the individual attributes here constitues the complete config for
* this deployment tool.
*/
private EjbJar.Config config;
/** Stores a handle to the directory to put the Jar files in */
private File destDir;
/** The classpath to use with this deployment tool. This is appended to
any paths from the ejbjar task itself.*/
private Path classpath;
/** Instance variable that stores the suffix for the generated jarfile. */
private String genericJarSuffix = "-generic.jar";
/**
* 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.
* @param inDir the destination directory.
*/
public void setDestdir(File inDir) {
this.destDir = inDir;
}
/**
* Get the desitination directory.
*/
protected File getDestDir() {
return destDir;
}
/**
* Set the task which owns this tool
*/
public void setTask(Task task) {
this.task = task;
}
/**
* Get the task for this tool.
*/
protected Task getTask() {
return task;
}
/**
* Get the basename terminator.
*/
protected EjbJar.Config getConfig() {
return config;
}
/**
* Returns true, if the meta-inf dir is being explicitly set, false otherwise.
*/
protected boolean usingBaseJarName() {
return config.baseJarName != null;
}
/**
* Setter used to store the suffix for the generated jar file.
* @param inString the string to use as the suffix.
*/
public void setGenericJarSuffix(String inString) {
this.genericJarSuffix = inString;
}
/**
* Add the classpath for the user classes
*/
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;
}
/**
* Get the classpath by combining the one from the surrounding task, if any
* and the one from tis tool.
*/
protected Path getCombinedClasspath() {
Path combinedPath = classpath;
if (config.classpath != null) {
if (combinedPath == null) {
combinedPath = config.classpath;
}
else {
combinedPath.append(config.classpath);
}
}
return combinedPath;
}
protected void log(String message, int level) {
getTask().log(message, level);
}
/**
* Configure this tool for use in the ejbjar task.
*/
public void configure(EjbJar.Config config) {
this.config = config;
classpathLoader = null;
}
/**
* 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 inputFile A File from which to read the
* contents the file being added.
* @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,
File inputFile,
String logicalFilename)
throws BuildException {
FileInputStream iStream = null;
try {
if (!addedfiles.contains(logicalFilename)) {
iStream = new FileInputStream(inputFile);
// Create the zip entry and add it to the jar file
ZipEntry zipEntry = new ZipEntry(logicalFilename.replace('\\','/'));
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 "
+ 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) {
return new DescriptorHandler(srcDir);
}
public void processDescriptor(String descriptorFileName, SAXParser saxParser) {
FileInputStream descriptorStream = null;
try {
DescriptorHandler handler = getDescriptorHandler(config.srcDir);
/* 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.
*/
descriptorStream = new FileInputStream(new File(config.descriptorDir, descriptorFileName));
saxParser.parse(new InputSource(descriptorStream), handler);
Hashtable ejbFiles = handler.getFiles();
// add in support classes if any
if (config.supportFileSet != null) {
Project project = task.getProject();
File supportBaseDir = config.supportFileSet.getDir(project);
DirectoryScanner supportScanner = config.supportFileSet.getDirectoryScanner(project);
supportScanner.scan();
String[] supportFiles = supportScanner.getIncludedFiles();
for (int i = 0; i < supportFiles.length; ++i) {
ejbFiles.put(supportFiles[i], new File(supportBaseDir, supportFiles[i]));
}
}
String baseName = "";
// Work out what the base name is
if (config.baseJarName != null) {
baseName = config.baseJarName;
} else {
int lastSeparatorIndex = descriptorFileName.lastIndexOf(File.separator);
int endBaseName = -1;
if (lastSeparatorIndex != -1) {
endBaseName = descriptorFileName.indexOf(config.baseNameTerminator,
lastSeparatorIndex);
}
else {
endBaseName = descriptorFileName.indexOf(config.baseNameTerminator);
}
if (endBaseName != -1) {
baseName = descriptorFileName.substring(0, endBaseName);
}
baseName = descriptorFileName.substring(0, endBaseName);
}
// First the regular deployment descriptor
ejbFiles.put(META_DIR + EJB_DD,
new File(config.descriptorDir, 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!
if (config.flatDestDir && baseName.length() != 0) {
int startName = baseName.lastIndexOf(File.separator);
if (startName == -1) {
startName = 0;
}
int endName = baseName.length();
baseName = baseName.substring(startName, endName);
}
File jarFile = getVendorOutputJarFile(baseName);
// By default we assume we need to build.
boolean needBuild = true;
if (jarFile.exists()) {
long lastBuild = jarFile.lastModified();
Iterator fileIter = ejbFiles.values().iterator();
// 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()) ) {
File currentFile = (File) fileIter.next();
needBuild = ( lastBuild < currentFile.lastModified() );
if (needBuild) {
log("Build needed because " + currentFile.getPath() + " is out of date",
Project.MSG_VERBOSE);
}
}
}
// Check to see if we need a build and start
// doing the work!
if (needBuild) {
// Log that we are going to build...
log( "building "
+ jarFile.getName()
+ " with "
+ String.valueOf(ejbFiles.size())
+ " files",
Project.MSG_INFO);
// Use helper method to write the jarfile
writeJar(baseName, jarFile, ejbFiles);
}
else {
// Log that the file is up to date...
log(jarFile.toString() + " is up to date.",
Project.MSG_VERBOSE);
}
}
catch (SAXException se) {
String msg = "SAXException while parsing '"
+ descriptorFileName.toString()
+ "'. This probably indicates badly-formed XML."
+ " Details: "
+ se.getMessage();
throw new BuildException(msg, se);
}
catch (IOException ioe) {
String msg = "IOException while parsing'"
+ descriptorFileName.toString()
+ "'. This probably indicates that the descriptor"
+ " doesn't exist. Details: "
+ ioe.getMessage();
throw new BuildException(msg, ioe);
}
finally {
if (descriptorStream != null) {
try {
descriptorStream.close();
}
catch (IOException closeException) {}
}
}
}
/**
* Add any vendor specific files which should be included in the
* EJB Jar.
*/
protected void addVendorFiles(Hashtable ejbFiles, String baseName) {
// nothing to add for generic tool.
}
/**
* Get the vendor specific name of the Jar that will be output. The modification date
* of this jar will be checked against the dependent bean classes.
*/
File getVendorOutputJarFile(String baseName) {
return new File(destDir, baseName + genericJarSuffix);
}
/**
* 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.
*/
protected void writeJar(String baseName, File jarfile, Hashtable files) throws BuildException{
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
* 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
for (Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext(); ) {
String entryName = (String) entryIterator.next();
File entryFile = (File) files.get(entryName);
log("adding file '" + entryName + "'",
Project.MSG_VERBOSE);
addFileToJar(jarStream, 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(config.srcDir, entryName);
log("adding innerclass file '" + entryName + "'",
Project.MSG_VERBOSE);
addFileToJar(jarStream, entryFile, entryName);
}
}
}
catch(IOException ioe) {
String msg = "IOException while processing ejb-jar file '"
+ jarfile.toString()
+ "'. Details: "
+ 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);
addInterface(interfaces[i], checkEntries);
}
}
else // get as a class
{
log("looking at class " + c.getName(), Project.MSG_VERBOSE);
Class s = c.getSuperclass();
addSuperClass(c.getSuperclass(), checkEntries);
}
} //if primative
}
catch (ClassNotFoundException cnfe) {
log("Could not load class " + classname + " for super class check",
Project.MSG_WARN);
}
catch (NoClassDefFoundError ncdfe) {
log("Could not fully load class " + classname + " for super class check",
Project.MSG_WARN);
}
} //if
} // while
}
private void addInterface(Class theInterface, Hashtable checkEntries) {
if (!theInterface.getName().startsWith("java")) // do not add system interfaces
{
File interfaceFile = new File(config.srcDir.getAbsolutePath()
+ File.separatorChar
+ theInterface.getName().replace('.',File.separatorChar)
+ ".class"
);
if (interfaceFile.exists() && interfaceFile.isFile())
{
checkEntries.put(theInterface.getName().replace('.',File.separatorChar)+".class",
interfaceFile);
Class[] superInterfaces = theInterface.getInterfaces();
for (int i = 0; i < superInterfaces.length; i++) {
addInterface(superInterfaces[i], checkEntries);
}
}
}
}
private void addSuperClass(Class superClass, Hashtable checkEntries) {
if (!superClass.getName().startsWith("java"))
{
File superClassFile = new File(config.srcDir.getAbsolutePath()
+ File.separatorChar
+ superClass.getName().replace('.',File.separatorChar)
+ ".class");
if (superClassFile.exists() && superClassFile.isFile())
{
checkEntries.put(superClass.getName().replace('.',File.separatorChar) + ".class",
superClassFile);
// now need to get super classes and interfaces for this class
Class[] superInterfaces = superClass.getInterfaces();
for (int i = 0; i < superInterfaces.length; i++) {
addInterface(superInterfaces[i], checkEntries);
}
addSuperClass(superClass.getSuperclass(), checkEntries);
}
}
}
/**
* 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;
}
Path combinedClasspath = getCombinedClasspath();
// only generate a new ClassLoader if we have a classpath
if (combinedClasspath == null) {
classpathLoader = getClass().getClassLoader();
}
else {
classpathLoader = new AntClassLoader(getTask().getProject(), combinedClasspath);
}
return classpathLoader;
}
/**
* Called to validate that the tool parameters have been configured.
*
*/
public void validateConfigured() throws BuildException {
if (destDir == null) {
throw new BuildException("The destdir attribute must be specified");
}
}
}