From a80cf0dfece4c5bd7a1dd24b05a8d30bf441abf5 Mon Sep 17 00:00:00 2001 From: Conor MacNeill Date: Tue, 1 May 2001 10:31:48 +0000 Subject: [PATCH] iplanet-ejbc task for EJB stubs and skeletons for the iPlanet Application Server 6.0 Nested element to support iPlanet in ejbjar task Submitted by: Greg Nelson git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@268990 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 1 + .../tools/ant/taskdefs/defaults.properties | 1 + .../ant/taskdefs/optional/ejb/EjbJar.java | 15 + .../optional/ejb/GenericDeploymentTool.java | 271 ++- .../optional/ejb/IPlanetDeploymentTool.java | 423 +++++ .../taskdefs/optional/ejb/IPlanetEjbc.java | 1489 +++++++++++++++++ .../optional/ejb/IPlanetEjbcTask.java | 353 ++++ 7 files changed, 2480 insertions(+), 73 deletions(-) create mode 100644 src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java create mode 100644 src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java create mode 100644 src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java diff --git a/build.xml b/build.xml index 8a5ae227c..65e71dd4b 100644 --- a/build.xml +++ b/build.xml @@ -189,6 +189,7 @@ + diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index 3bcbd4054..6c2d53647 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -100,6 +100,7 @@ rpm=org.apache.tools.ant.taskdefs.optional.Rpm xmlvalidate=org.apache.tools.ant.taskdefs.optional.XMLValidateTask vsscheckin=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKIN vsscheckout=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKOUT +iplanet-ejbc=org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbcTask # deprecated ant tasks (kept for back compatibility) javadoc2=org.apache.tools.ant.taskdefs.Javadoc 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 923ea398d..5b2dc1742 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 @@ -208,6 +208,21 @@ public class EjbJar extends MatchingTask { return tool; } + /** + * Create a nested element used to configure a deployment tool for iPlanet + * Application Server. + * + * @return the deployment tool instance to be configured. + */ + public IPlanetDeploymentTool createIplanet() { + log("iPlanet Application Server deployment tools", Project.MSG_VERBOSE); + + IPlanetDeploymentTool tool = new IPlanetDeploymentTool(); + tool.setTask(this); + deploymentTools.add(tool); + return tool; + } + /** * Create a nested element for weblogic when using the Toplink * Object- Relational mapping. 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 8ceacc46a..fcb8de496 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 @@ -113,6 +113,11 @@ public class GenericDeploymentTool implements EJBDeploymentTool { */ private List addedfiles; + /** + * Handler used to parse the EJB XML descriptor + */ + private DescriptorHandler handler; + /** * Setter used to store the value of destination directory prior to execute() * being called. @@ -205,6 +210,10 @@ public class GenericDeploymentTool implements EJBDeploymentTool { getTask().log(message, level); } + protected Location getLocation() { + return getTask().getLocation(); + } + /** * Configure this tool for use in the ejbjar task. @@ -293,54 +302,18 @@ public class GenericDeploymentTool implements EJBDeploymentTool { } 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(); + checkConfiguration(descriptorFileName, saxParser); - // add in support classes if any - Project project = task.getProject(); - for (Iterator i = config.supportFileSets.iterator(); i.hasNext();) { - FileSet supportFileSet = (FileSet)i.next(); - File supportBaseDir = supportFileSet.getDir(project); - DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner(project); - supportScanner.scan(); - String[] supportFiles = supportScanner.getIncludedFiles(); - for (int j = 0; j < supportFiles.length; ++j) { - ejbFiles.put(supportFiles[j], new File(supportBaseDir, supportFiles[j])); - } - } + try { + // Retrive the files to be added to JAR from EJB descriptor + Hashtable ejbFiles = parseEjbFiles(descriptorFileName, saxParser); - 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); - } + // Add any support classes specified in the build file + addSupportClasses(ejbFiles); - if (endBaseName != -1) { - baseName = descriptorFileName.substring(0, endBaseName); - } - baseName = descriptorFileName.substring(0, endBaseName); - } + // Determine the JAR filename (without filename extension) + String baseName = getJarBaseName(descriptorFileName); // First the regular deployment descriptor ejbFiles.put(META_DIR + EJB_DD, @@ -366,30 +339,9 @@ public class GenericDeploymentTool implements EJBDeploymentTool { 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) { + // Check to see if we need a build and start doing the work! + if (needToRebuild(ejbFiles, jarFile)) { // Log that we are going to build... log( "building " + jarFile.getName() @@ -399,7 +351,8 @@ public class GenericDeploymentTool implements EJBDeploymentTool { Project.MSG_INFO); // Use helper method to write the jarfile - writeJar(baseName, jarFile, ejbFiles, handler.getPublicId()); + String publicId = getPublicId(); + writeJar(baseName, jarFile, ejbFiles, publicId); } else { @@ -425,7 +378,63 @@ public class GenericDeploymentTool implements EJBDeploymentTool { + ioe.getMessage(); throw new BuildException(msg, ioe); } - finally { + } + + /** + * This method is called as the first step in the processDescriptor method + * to allow vendor-specific subclasses to validate the task configuration + * prior to processing the descriptor. If the configuration is invalid, + * a BuildException should be thrown. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML + * descriptor + * @thows BuildException Thrown if the configuration is invalid + */ + protected void checkConfiguration(String descriptorFileName, + SAXParser saxParser) throws BuildException { + + /* + * For the GenericDeploymentTool, do nothing. Vendor specific + * subclasses should throw a BuildException if the configuration is + * invalid for their server. + */ + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML + * descriptor + * @return Hashtable of EJB class (and other) files to be + * added to the completed JAR file + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + * @throws IOException An IOException from the parser, possibly from a + * the byte stream or character stream + */ + protected Hashtable parseEjbFiles(String descriptorFileName, SAXParser saxParser) + throws IOException, SAXException { + FileInputStream descriptorStream = null; + Hashtable ejbFiles = null; + + try { + 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); + + ejbFiles = handler.getFiles(); + + } finally { if (descriptorStream != null) { try { descriptorStream.close(); @@ -433,8 +442,70 @@ public class GenericDeploymentTool implements EJBDeploymentTool { catch (IOException closeException) {} } } - } - + + return ejbFiles; + } + + /** + * Adds any classes the user specifies using support nested elements + * to the ejbFiles Hashtable. + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + */ + protected void addSupportClasses(Hashtable ejbFiles) { + // add in support classes if any + Project project = task.getProject(); + for (Iterator i = config.supportFileSets.iterator(); i.hasNext();) { + FileSet supportFileSet = (FileSet)i.next(); + File supportBaseDir = supportFileSet.getDir(project); + DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner(project); + supportScanner.scan(); + String[] supportFiles = supportScanner.getIncludedFiles(); + for (int j = 0; j < supportFiles.length; ++j) { + ejbFiles.put(supportFiles[j], new File(supportBaseDir, supportFiles[j])); + } + } + } + + + /** + * Using the EJB descriptor file name passed from the ejbjar + * task, this method returns the "basename" which will be used to name the + * completed JAR file. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @return The "basename" which will be used to name the + * completed JAR file + */ + protected String getJarBaseName(String descriptorFileName) { + + 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); + } + + return baseName; + } + + /** * Add any vendor specific files which should be included in the * EJB Jar. @@ -452,6 +523,56 @@ public class GenericDeploymentTool implements EJBDeploymentTool { return new File(destDir, baseName + genericJarSuffix); } + /** + * This method checks the timestamp on each file listed in the + * ejbFiles and compares them to the timestamp on the jarFile + * . If the jarFile's timestamp is more recent than + * each EJB file, false is returned. Otherwise, true + * is returned. + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + * @param jarFile JAR file which will contain all of the EJB classes (and + * other) files + * @return boolean indicating whether or not the jarFile + * is up to date + */ + protected boolean needToRebuild(Hashtable ejbFiles, File jarFile) { + // 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); + } + } + } + + return needBuild; + } + + /** + * Returns the Public ID of the DTD specified in the EJB descriptor. Not + * every vendor-specific DeploymentTool will need to reference + * this value or may want to determine this value in a vendor-specific way. + * + * @return Public ID of the DTD specified in the EJB descriptor. + */ + protected String getPublicId() { + return handler.getPublicId(); + } + /** * 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 @@ -664,10 +785,14 @@ public class GenericDeploymentTool implements EJBDeploymentTool { /** * Called to validate that the tool parameters have been configured. * + * @throws BuildException If the Deployment Tool's configuration isn't + * valid */ public void validateConfigured() throws BuildException { - if (destDir == null) { - throw new BuildException("The destdir attribute must be specified"); + if ((destDir == null) || (!destDir.isDirectory())) { + String msg = "A valid destination directory must be specified " + + "using the \"destdir\" attribute."; + throw new BuildException(msg, getLocation()); } } } diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java new file mode 100644 index 000000000..6e8db54dc --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java @@ -0,0 +1,423 @@ +/* + * 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 + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ejb; + +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; + +import javax.xml.parsers.SAXParser; +import org.xml.sax.SAXException; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.BuildException; + +/** + * This class is used to generate iPlanet Application Server (iAS) 6.0 stubs and + * skeletons and build an EJB Jar file. It is designed to be used with the Ant + * ejbjar task. If only stubs and skeletons need to be generated + * (in other words, if no JAR file needs to be created), refer to the + * iplanet-ejbc task and the IPlanetEjbcTask class. + *

+ * The following attributes may be specified by the user: + *

    + *
  • destdir -- The base directory into which the generated JAR + * files will be written. Each JAR file is written + * in directories which correspond to their location + * within the "descriptordir" namespace. This is a + * required attribute. + *
  • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if + * omitted, the classpath specified in the "ejbjar" + * parent task will be used). If specified, the + * classpath elements will be prepended to the + * classpath specified in the parent "ejbjar" task. + * Note that nested "classpath" elements may also be + * used. + *
  • keepgenerated -- Indicates whether or not the Java source + * files which are generated by ejbc will be + * saved or automatically deleted. If "yes", + * the source files will be retained. This is + * an optional attribute (if omitted, it + * defaults to "no"). + *
  • debug -- Indicates whether or not the ejbc utility should + * log additional debugging statements to the standard + * output. If "yes", the additional debugging statements + * will be generated (if omitted, it defaults to "no"). + *
  • iashome -- May be used to specify the "home" directory for + * this iPlanet Application server installation. This + * is used to find the ejbc utility if it isn't + * included in the user's system path. This is an + * optional attribute (if specified, it should refer + * to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
  • suffix -- String value appended to the JAR filename when + * creating each JAR. This attribute is not required + * (if omitted, it defaults to ".jar"). + *
+ *

+ * For each EJB descriptor found in the "ejbjar" parent task, this deployment + * tool will locate the three classes that comprise the EJB. If these class + * files cannot be located in the specified srcdir directory, the + * task will fail. The task will also attempt to locate the EJB stubs and + * skeletons in this directory. If found, the timestamps on the stubs and + * skeletons will be checked to ensure they are up to date. Only if these files + * cannot be found or if they are out of date will ejbc be called. + * + * @see IPlanetEjbc + * @author Greg Nelson greg@netscape.com + */ +public class IPlanetDeploymentTool extends GenericDeploymentTool { + + /* Attributes set by the Ant build file */ + private File iashome; + private String jarSuffix = ".jar"; + private boolean keepgenerated = false; + private boolean debug = false; + + /* + * Filenames of the standard EJB descriptor (which is passed to this class + * from the parent "ejbjar" task) and the iAS-specific EJB descriptor + * (whose name is determined by this class). Both filenames are relative + * to the directory specified by the "srcdir" attribute in the ejbjar task. + */ + private String descriptorName; + private String iasDescriptorName; + + /* + * The displayName variable stores the value of the "display-name" element + * from the standard EJB descriptor. As a future enhancement to this task, + * we may determine the name of the EJB JAR file using this display-name, + * but this has not be implemented yet. + */ + private String displayName; + + /* + * Regardless of the name of the iAS-specific EJB descriptor file, it will + * written in the completed JAR file as "ias-ejb-jar.xml". This is the + * naming convention implemented by iAS. + */ + private static final String IAS_DD = "ias-ejb-jar.xml"; + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be + * [install-location]/iplanet/ias6/ias. + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome(File iashome) { + this.iashome = iashome; + } + + /** + * Setter method used to specify whether the Java source files generated by + * the ejbc utility should be saved or automatically deleted. + * + * @param keepgenerated boolean which, if true, indicates that + * Java source files generated by ejbc for the stubs + * and skeletons should be kept. + */ + public void setKeepgenerated(boolean keepgenerated) { + this.keepgenerated = keepgenerated; + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug(boolean debug) { + this.debug = debug; + } + + /** + * Setter method used to specify the filename suffix (for example, ".jar") + * for the JAR files to be created. + * + * @param jarSuffix The string to use as the JAR filename suffix. + */ + public void setSuffix(String jarSuffix) { + this.jarSuffix = jarSuffix; + } + + /** + * Since iAS doesn't generate a "generic" JAR as part of its processing, + * this attribute is ignored and a warning message is displayed to the user. + * + * @param inString the string to use as the suffix. This parameter is + * ignored. + */ + public void setGenericJarSuffix(String inString) { + log("Since a generic JAR file is not created during processing, the " + + "iPlanet Deployment Tool does not support the " + + "\"genericjarsuffix\" attribute. It will be ignored.", + Project.MSG_WARN); + } + + public void processDescriptor(String descriptorName, SAXParser saxParser) { + this.descriptorName = descriptorName; + + log("iPlanet Deployment Tool processing: " + descriptorName + " (and " + + getIasDescriptorName() + ")", Project.MSG_VERBOSE); + + super.processDescriptor(descriptorName, saxParser); + } + + /** + * Verifies that the user selections are valid. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML + * descriptor + * @throws BuildException If the user selections are invalid. + */ + protected void checkConfiguration(String descriptorFileName, + SAXParser saxParser) throws BuildException { + + int startOfName = descriptorFileName.lastIndexOf(File.separatorChar) + 1; + String stdXml = descriptorFileName.substring(startOfName); + if (stdXml.equals(EJB_DD) && (getConfig().baseJarName == null)) { + String msg = "No name specified for the completed JAR file. The EJB" + + " descriptor should be prepended with the JAR " + + "name or it should be specified using the " + + "attribute \"basejarname\" in the \"ejbjar\" task."; + throw new BuildException(msg, getLocation()); + } + + File iasDescriptor = new File(getConfig().descriptorDir, + getIasDescriptorName()); + if ((!iasDescriptor.exists()) || (!iasDescriptor.isFile())) { + String msg = "The iAS-specific EJB descriptor (" + + iasDescriptor + ") was not found."; + throw new BuildException(msg, getLocation()); + } + + if ((iashome != null) && (!iashome.isDirectory())) { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException(msg, getLocation()); + } + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML + * descriptor + * @return Hashtable of EJB class (and other) files to be + * added to the completed JAR file + * @throws IOException An IOException from the parser, possibly from + * the byte stream or character stream + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + */ + protected Hashtable parseEjbFiles(String descriptorFileName, + SAXParser saxParser) throws IOException, SAXException { + + Hashtable files; + + /* Build and populate an instance of the ejbc utility */ + IPlanetEjbc ejbc = new IPlanetEjbc( + new File(getConfig().descriptorDir, + descriptorFileName), + new File(getConfig().descriptorDir, + getIasDescriptorName()), + getConfig().srcDir, + getCombinedClasspath().toString(), + saxParser); + ejbc.setRetainSource(keepgenerated); + ejbc.setDebugOutput(debug); + if (iashome != null) { + ejbc.setIasHomeDir(iashome); + } + + /* Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed */ + try { + ejbc.execute(); + } catch (IPlanetEjbc.EjbcException e) { + throw new BuildException("An error has occurred while trying to " + + "execute the iAS ejbc utility", e, getLocation()); + } + + displayName = ejbc.getDisplayName(); + files = ejbc.getEjbFiles(); + + /* Add CMP descriptors to the list of EJB files */ + String[] cmpDescriptors = ejbc.getCmpDescriptors(); + if (cmpDescriptors.length > 0) { + File baseDir = getConfig().descriptorDir; + + int endOfPath = descriptorFileName.lastIndexOf(File.separator); + String relativePath = descriptorFileName.substring(0, endOfPath + 1); + + for (int i = 0; i < cmpDescriptors.length; i++) { + int endOfCmp = cmpDescriptors[i].lastIndexOf('/'); + String cmpDescriptor = cmpDescriptors[i].substring(endOfCmp + 1); + + File cmpFile = new File(baseDir, relativePath + cmpDescriptor); + if (!cmpFile.exists()) { + throw new BuildException("The CMP descriptor file (" + + cmpFile + ") could not be found.", getLocation()); + } + files.put(cmpDescriptors[i], cmpFile); + } + } + + return files; + } + + /** + * Add the iAS-specific EJB descriptor to the list of files which will be + * written to the JAR file. + * + * @param ejbFiles Hashtable of EJB class (and other) files to be added to + * the completed JAR file. + * @param baseName String name of the EJB JAR file to be written (without + * a filename extension). + */ + protected void addVendorFiles(Hashtable ejbFiles, String baseName) { + + ejbFiles.put(META_DIR + IAS_DD, new File(getConfig().descriptorDir, + getIasDescriptorName())); + } + + /** + * Get the name of the Jar that will be written. The modification date + * of this jar will be checked against the dependent bean classes. + * + * @param baseName String name of the EJB JAR file to be written (without + * a filename extension). + * + * @return File representing the JAR file which will be written. + */ + File getVendorOutputJarFile(String baseName) { + File jarFile = new File(getDestDir(), baseName + jarSuffix); + log("JAR file name: " + jarFile.toString(), Project.MSG_VERBOSE); + return jarFile; + } + + /** + * The iAS ejbc utility doesn't require the Public ID of the descriptor's + * DTD for it to process correctly--this method always returns null + * . + * + * @return null. + */ + protected String getPublicId() { + return null; + } + + /** + * Determines the name of the iAS-specific EJB descriptor using the + * specified standard EJB descriptor name. In general, the standard + * descriptor will be named "[basename]-ejb-jar.xml", and this method will + * return "[basename]-ias-ejb-jar.xml". + * + * @return The name of the iAS-specific EJB descriptor file. + */ + private String getIasDescriptorName() { + + /* Only calculate the descriptor name once */ + if (iasDescriptorName != null) { + return iasDescriptorName; + } + + String path = ""; // Directory path of the EJB descriptor + String basename; // Filename appearing before name terminator + String remainder; // Filename appearing after the name terminator + + /* Find the end of the standard descriptor's relative path */ + int startOfFileName = descriptorName.lastIndexOf(File.separatorChar); + if (startOfFileName != -1) { + path = descriptorName.substring(0, startOfFileName + 1); + } + + /* Check to see if the standard name is used (there's no basename) */ + if (descriptorName.substring(startOfFileName + 1).equals(EJB_DD)) { + basename = ""; + remainder = EJB_DD; + + } else { + int endOfBaseName = descriptorName.indexOf( + getConfig().baseNameTerminator, + startOfFileName); + /* + * Check for the odd case where the terminator and/or filename + * extension aren't found. These will ensure "ias-" appears at the + * end of the name and before the '.' (if present). + */ + if (endOfBaseName < 0) { + endOfBaseName = descriptorName.lastIndexOf('.') - 1; + if (endOfBaseName < 0) { + endOfBaseName = descriptorName.length() - 1; + } + } + + basename = descriptorName.substring(startOfFileName + 1, + endOfBaseName + 1); + remainder = descriptorName.substring(endOfBaseName + 1); + } + + iasDescriptorName = path + basename + "ias-" + remainder; + return iasDescriptorName; + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java new file mode 100644 index 000000000..00da15623 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java @@ -0,0 +1,1489 @@ +/* + * 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 + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ejb; + +import java.io.File; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.Properties; +import java.util.Date; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.HandlerBase; +import org.xml.sax.SAXException; +import org.xml.sax.InputSource; +import org.xml.sax.AttributeList; + +/** + * Utility class to compile EJB stubs and skeletons for the iPlanet Application + * Server (iAS). The class will read a standard EJB descriptor (as well as an + * EJB descriptor specific to iPlanet Application Server) to identify one or + * more EJBs to process. It will search for EJB "source" classes (the remote + * interface, home interface, and EJB implementation class) and the EJB stubs + * and skeletons in the specified destination directory. Only if the stubs and + * skeletons cannot be found or if they're out of date will the iPlanet + * Application Server ejbc utility be run. + *

+ * Because this class (and it's assorted inner classes) may be bundled into the + * iPlanet Application Server distribution at some point (and removed from the + * Ant distribution), the class has been written to be independent of all + * Ant-specific classes. It is also for this reason (and to avoid cluttering + * the Apache Ant source files) that this utility has been packaged into a + * single source file. + *

+ * For more information on Ant Tasks for iPlanet Application Server, see the + * IPlanetDeploymentTool and IPlanetEjbcTask classes. + * + * @see IPlanetDeploymentTool + * @see IPlanetEjbcTask + * @author Greg Nelson greg@netscape.com + */ +public class IPlanetEjbc { + + /* Constants used for the "beantype" attribute */ + private static final String ENTITY_BEAN = "entity"; + private static final String STATELESS_SESSION = "stateless"; + private static final String STATEFUL_SESSION = "stateful"; + + /* Filenames of the standard EJB descriptor and the iAS-specific descriptor */ + private File stdDescriptor; + private File iasDescriptor; + + /* + * Directory where "source" EJB files are stored and where stubs and + * skeletons will also be written. + */ + private File destDirectory; + + /* Classpath used when the iAS ejbc is called */ + private String classpath; + private String[] classpathElements; + + /* Options passed to the iAS ejbc */ + private boolean retainSource = false; + private boolean debugOutput = false; + + /* iAS installation directory (used if ejbc isn't on user's PATH) */ + private File iasHomeDir; + + /* Parser and handler used to process both EJB descriptor files */ + private SAXParser parser; + private EjbcHandler handler = new EjbcHandler(); + + /* + * This Hashtable maintains a list of EJB class files processed by the ejbc + * utility (both "source" class files as well as stubs and skeletons). The + * key for the Hashtable is a String representing the path to the class file + * (relative to the destination directory). The value for the Hashtable is + * a File object which reference the actual class file. + */ + private Hashtable ejbFiles = new Hashtable(); + + /* Value of the display-name element read from the standard EJB descriptor */ + private String displayName; + + /** + * Constructs an instance which may be used to process EJB descriptors and + * generate EJB stubs and skeletons, if needed. + * + * @param stdDescriptor File referencing a standard EJB descriptor. + * @param iasDescriptor File referencing an iAS-specific EJB descriptor. + * @param destDirectory File referencing the base directory where both + * EJB "source" files are found and where stubs and + * skeletons will be written. + * @param classpath String representation of the classpath to be used + * by the iAS ejbc utility. + * @param parser SAXParser to be used to process both of the EJB + * descriptors. + */ + public IPlanetEjbc(File stdDescriptor, + File iasDescriptor, + File destDirectory, + String classpath, + SAXParser parser) { + this.stdDescriptor = stdDescriptor; + this.iasDescriptor = iasDescriptor; + this.destDirectory = destDirectory; + this.classpath = classpath; + this.parser = parser; + + /* + * Parse the classpath into it's individual elements and store the + * results in the "classpathElements" instance variable. + */ + List elements = new ArrayList(); + if (classpath != null) { + StringTokenizer st = new StringTokenizer(classpath, + File.pathSeparator); + while (st.hasMoreTokens()) { + elements.add(st.nextToken()); + } + classpathElements + = (String[])elements.toArray(new String[elements.size()]); + } + } + + /** + * Sets whether or not the Java source files which are generated by the + * ejbc process should be retained or automatically deleted. + * + * @param retainsource A boolean indicating if the Java source files for + * the stubs and skeletons should be retained. + */ + public void setRetainSource(boolean retainSource) { + this.retainSource = retainSource; + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debugOutput A boolean indicating if debugging output should be + * generated + */ + public void setDebugOutput(boolean debugOutput) { + this.debugOutput = debugOutput; + } + + /** + * Registers the location of a local DTD file or resource. By registering + * a local DTD, EJB descriptors can be parsed even when the remote servers + * which contain the "public" DTDs cannot be accessed. + * + * @param publicID The public DTD identifier found in an XML document. + * @param location The file or resource name for the appropriate DTD stored + * on the local machine. + */ + public void registerDTD(String publicID, String location) { + handler.registerDTD(publicID, location); + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be + * [install-location]/iplanet/ias6/ias. + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIasHomeDir(File iasHomeDir) { + this.iasHomeDir = iasHomeDir; + } + + /** + * Returns a Hashtable which contains a list of EJB class files processed by + * the ejbc utility (both "source" class files as well as stubs and + * skeletons). The key for the Hashtable is a String representing the path + * to the class file (relative to the destination directory). The value for + * the Hashtable is a File object which reference the actual class file. + * + * @return The list of EJB files processed by the ejbc utility. + */ + public Hashtable getEjbFiles() { + return ejbFiles; + } + + /** + * Returns the display-name element read from the standard EJB descriptor. + * + * @return The EJB-JAR display name. + */ + public String getDisplayName() { + return displayName; + } + + /** + * Returns the list of CMP descriptors referenced in the EJB descriptors. + * + * @return An array of CMP descriptors. + */ + public String[] getCmpDescriptors() { + List returnList = new ArrayList(); + + EjbInfo[] ejbs = handler.getEjbs(); + + for (int i = 0; i < ejbs.length ; i++) { + List descriptors = (List) ejbs[i].getCmpDescriptors(); + returnList.addAll(descriptors); + } + + return (String[]) returnList.toArray(new String[returnList.size()]); + } + + /** + * Main application method for the iPlanet Application Server ejbc utility. + * If the application is run with no commandline arguments, a usage + * statement is printed for the user. + * + * @param args The commandline arguments passed to the application. + */ + public static void main(String[] args) { + File stdDescriptor; + File iasDescriptor; + File destDirectory = null; + String classpath = null; + SAXParser parser = null; + boolean debug = false; + boolean retainSource = false; + IPlanetEjbc ejbc; + + if ((args.length < 2) || (args.length > 8)) { + usage(); + return; + } + + stdDescriptor = new File(args[args.length - 2]); + iasDescriptor = new File(args[args.length - 1]); + + for (int i = 0; i < args.length - 2; i++) { + if (args[i].equals("-classpath")) { + classpath = args[++i]; + } else if (args[i].equals("-d")) { + destDirectory = new File(args[++i]); + } else if (args[i].equals("-debug")) { + debug = true; + } else if (args[i].equals("-keepsource")) { + retainSource = true; + } else { + usage(); + return; + } + } + + /* If the -classpath flag isn't specified, use the system classpath */ + if (classpath == null) { + Properties props = System.getProperties(); + classpath = props.getProperty("java.class.path"); + } + + /* + * If the -d flag isn't specified, use the working directory as the + * destination directory + */ + if (destDirectory == null) { + Properties props = System.getProperties(); + destDirectory = new File(props.getProperty("user.dir")); + } + + /* Construct a SAXParser used to process the descriptors */ + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setValidating(true); + try { + parser = parserFactory.newSAXParser(); + } catch (Exception e) { + // SAXException or ParserConfigurationException may be thrown + System.out.println("An exception was generated while trying to "); + System.out.println("create a new SAXParser."); + e.printStackTrace(); + return; + } + + /* Build and populate an instance of the ejbc utility */ + ejbc = new IPlanetEjbc(stdDescriptor, iasDescriptor, destDirectory, + classpath, parser); + ejbc.setDebugOutput(debug); + ejbc.setRetainSource(retainSource); + + /* Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed */ + try { + ejbc.execute(); + } catch (IOException e) { + System.out.println("An IOException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")."); + return; + } catch (SAXException e) { + System.out.println("A SAXException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")."); + return; + } catch (IPlanetEjbc.EjbcException e) { + System.out.println("An error has occurred while executing the ejbc " + + "utility (" + e.getMessage() + ")."); + return; + } + } + + /** + * Print a usage statement. + */ + private static void usage() { + System.out.println("java org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbc \\"); + System.out.println(" [OPTIONS] [EJB 1.1 descriptor] [iAS EJB descriptor]"); + System.out.println(""); + System.out.println("Where OPTIONS are:"); + System.out.println(" -debug -- for additional debugging output"); + System.out.println(" -keepsource -- to retain Java source files generated"); + System.out.println(" -classpath [classpath] -- classpath used for compilation"); + System.out.println(" -d [destination directory] -- directory for compiled classes"); + System.out.println(""); + System.out.println("If a classpath is not specified, the system classpath"); + System.out.println("will be used. If a destination directory is not specified,"); + System.out.println("the current working directory will be used (classes will"); + System.out.println("still be placed in subfolders which correspond to their"); + System.out.println("package name)."); + System.out.println(""); + System.out.println("The EJB home interface, remote interface, and implementation"); + System.out.println("class must be found in the destination directory. In"); + System.out.println("addition, the destination will look for the stubs and skeletons"); + System.out.println("in the destination directory to ensure they are up to date."); + } + + /** + * Compiles the stub and skeletons for the specified EJBs, if they need to + * be updated. + * + * @throws EjbcException If the ejbc utility cannot be correctly configured + * or if one or more of the EJB "source" classes + * cannot be found in the destination directory + * @throws IOException If the parser encounters a problem reading the XML + * file + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + public void execute() throws EjbcException, IOException, SAXException { + + checkConfiguration(); // Throws EjbcException if unsuccessful + + EjbInfo[] ejbs = getEjbs(); // Returns list of EJBs for processing + + for (int i = 0; i < ejbs.length ; i++) { + log("EJBInfo..."); + log(ejbs[i].toString()); + } + + for (int i=0; i < ejbs.length; i++) { + EjbInfo ejb = ejbs[i]; + + ejb.checkConfiguration(destDirectory); // Throws EjbcException + + if (ejb.mustBeRecompiled(destDirectory)) { + log(ejb.getName() + " must be recompiled using ejbc."); + + String[] arguments = buildArgumentList(ejb); + callEjbc(arguments); + + } else { + log(ejb.getName() + " is up to date."); + } + } + } + + /** + * Executes the iPlanet Application Server ejbc command-line utility. + * + * @param arguments Command line arguments to be passed to the ejbc utility. + */ + private void callEjbc(String[] arguments) { + + /* Concatenate all of the command line arguments into a single String */ + StringBuffer args = new StringBuffer(); + for (int i = 0; i < arguments.length; i++) { + args.append(arguments[i]).append(" "); + } + + /* If an iAS home directory is specified, prepend it to the commmand */ + String command; + if (iasHomeDir == null) { + command = ""; + } else { + command = iasHomeDir.toString() + File.separator + "bin" + + File.separator; + } + command += "ejbc "; + + log(command + args); + + /* + * Use the Runtime object to execute an external command. Use the + * RedirectOutput inner class to direct the standard and error output + * from the command to the JRE's standard output + */ + try { + Process p = Runtime.getRuntime().exec(command + args); + RedirectOutput output = new RedirectOutput(p.getInputStream()); + RedirectOutput error = new RedirectOutput(p.getErrorStream()); + output.start(); + error.start(); + p.waitFor(); + p.destroy(); + } catch (IOException e) { + log("An IOException has occurred while trying to execute ejbc."); + e.printStackTrace(); + } catch (InterruptedException e) { + // Do nothing + } + } + + /** + * Verifies that the user selections are valid. + * + * @throws EjbcException If the user selections are invalid. + */ + protected void checkConfiguration() throws EjbcException { + + String msg = ""; + + if (stdDescriptor == null) { + msg += "A standard XML descriptor file must be specified. "; + } + if (iasDescriptor == null) { + msg += "An iAS-specific XML descriptor file must be specified. "; + } + if (classpath == null) { + msg += "A classpath must be specified. "; + } + if (parser == null) { + msg += "An XML parser must be specified. "; + } + + if (destDirectory == null) { + msg += "A destination directory must be specified. "; + } else if (!destDirectory.exists()) { + msg += "The destination directory specified does not exist. "; + } else if (!destDirectory.isDirectory()) { + msg += "The destination specified is not a directory. "; + } + + if (msg.length() > 0) { + throw new EjbcException(msg); + } + } + + /** + * Parses the EJB descriptors and returns a list of EJBs which may need to + * be compiled. + * + * @return An array of objects which describe the EJBs to be + * processed. + * @throws IOException If the parser encounters a problem reading the XML + * files + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + private EjbInfo[] getEjbs() throws IOException, SAXException { + EjbInfo[] ejbs = null; + + /* + * The EJB information is gathered from the standard XML EJB descriptor + * and the iAS-specific XML EJB descriptor using a SAX parser. + */ + + parser.parse(stdDescriptor, handler); + parser.parse(iasDescriptor, handler); + ejbs = handler.getEjbs(); + + return ejbs; + } + + /** + * Based on this object's instance variables as well as the EJB to be + * processed, the correct flags and parameters are set for the ejbc + * command-line utility. + * @param ejb The EJB for which stubs and skeletons will be compiled. + * @return An array of Strings which are the command-line parameters for + * for the ejbc utility. + */ + private String[] buildArgumentList(EjbInfo ejb) { + + List arguments = new ArrayList(); + + /* OPTIONAL COMMAND LINE PARAMETERS */ + + if (debugOutput) { + arguments.add("-debug"); + } + + /* No beantype flag is needed for an entity bean */ + if (ejb.getBeantype().equals(STATELESS_SESSION)) { + arguments.add("-sl"); + } else if (ejb.getBeantype().equals(STATEFUL_SESSION)) { + arguments.add("-sf"); + } + + if (ejb.getIiop()) { + arguments.add("-iiop"); + } + + if (ejb.getCmp()) { + arguments.add("-cmp"); + } + + if (retainSource) { + arguments.add("-gs"); + } + + if (ejb.getHasession()) { + arguments.add("-fo"); + } + + /* REQUIRED COMMAND LINE PARAMETERS */ + + arguments.add("-classpath"); + arguments.add(classpath); + + arguments.add("-d"); + arguments.add(destDirectory.toString()); + + arguments.add(ejb.getHome().getQualifiedClassName()); + arguments.add(ejb.getRemote().getQualifiedClassName()); + arguments.add(ejb.getImplementation().getQualifiedClassName()); + + /* Convert the List into an Array and return it */ + return (String[])arguments.toArray(new String[arguments.size()]); + } + + /** + * Convenience method used to print messages to the user if debugging + * messages are enabled. + * + * @param msg The String to print to standard output. + */ + private void log(String msg) { + if (debugOutput) { + System.out.println(msg); + } + } + + + /* Inner classes follow */ + + + /** + * This inner class is used to signal any problems during the execution of + * the ejbc compiler. + * + * @author Greg Nelson + * greg@netscape.com + */ + public class EjbcException extends Exception { + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of the exception which has occurred. + */ + public EjbcException(String msg) { + super(msg); + } + } // End of EjbcException inner class + + + /** + * This inner class is an XML document handler that can be used to parse EJB + * descriptors (both the standard EJB descriptor as well as the iAS-specific + * descriptor that stores additional values for iAS). Once the descriptors + * have been processed, the list of EJBs found can be obtained by calling + * the getEjbs() method. + * + * @see EjbInfo + * @author Greg Nelson + * greg@netscape.com + */ + private class EjbcHandler extends HandlerBase { + + /* + * Two Maps are used to track local DTDs that will be used in case the + * remote copies of these DTDs cannot be accessed. The key for the Map + * is the DTDs public ID and the value is the local location for the DTD + */ + private Map resourceDtds = new HashMap(); + private Map fileDtds = new HashMap(); + + private Map ejbs = new HashMap(); // List of EJBs found in XML + private EjbInfo currentEjb; // One item within the Map + private boolean iasDescriptor = false; // Is doc iAS or EJB descriptor + + private String currentLoc = ""; // Tracks current element + private String currentText; // Tracks current text data + private String ejbType; // "session" or "entity" + + /** + * Constructs a new instance of the handler and registers local copies + * of the standard EJB 1.1 descriptor DTD as well as iAS's EJB + * descriptor DTD. + */ + public EjbcHandler() { + final String PUBLICID_EJB11 = + "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + final String PUBLICID_IPLANET_EJB_60 = + "-//Sun Microsystems, Inc.//DTD iAS Enterprise JavaBeans 1.0//EN"; + + + final String DEFAULT_IAS60_EJB11_DTD_LOCATION = + "ejb-jar_1_1.dtd"; + final String DEFAULT_IAS60_DTD_LOCATION = + "IASEjb_jar_1_0.dtd"; + + registerDTD(PUBLICID_EJB11, DEFAULT_IAS60_EJB11_DTD_LOCATION); + registerDTD(PUBLICID_IPLANET_EJB_60, DEFAULT_IAS60_DTD_LOCATION); + } + + /** + * Returns the list of EJB objects found during the processing of the + * standard EJB 1.1 descriptor and iAS-specific EJB descriptor. + * + * @return An array of EJBs which were found during the descriptor + * parsing. + */ + public EjbInfo[] getEjbs() { + return (EjbInfo[])ejbs.values().toArray(new EjbInfo[ejbs.size()]); + } + + /** + * Returns the value of the display-name element found in the standard + * EJB 1.1 descriptor. + * + * @return String display-name value. + */ + public String getDisplayName() { + return displayName; + } + + /** + * Registers a local DTD that will be used when parsing an EJB + * descriptor. When the DTD's public identifier is found in an XML + * document, the parser will reference the local DTD rather than the + * remote DTD. This enables XML documents to be processed even when the + * public DTD isn't available. + * + * @param publicID The DTD's public identifier. + * @param location The location of the local DTD copy -- the location + * may either be a resource found on the classpath or a + * local file. + */ + public void registerDTD(String publicID, String location) { + log("Registering: " + location); + if ((publicID == null) || (location == null)) { + return; + } + + if (ClassLoader.getSystemResource(location) != null) { + log("Found resource: " + location); + resourceDtds.put(publicID, location); + } else { + File dtdFile = new File(location); + if (dtdFile.exists() && dtdFile.isFile()) { + log("Found file: " + location); + fileDtds.put(publicID, location); + } + } + } + + /** + * Resolves an external entity found during XML processing. If a public + * ID is found that has been registered with the handler, an + * InputSource will be returned which refers to the local copy. + * If the public ID hasn't been registered or if an error occurs, the + * superclass implementation is used. + * + * @param publicId The DTD's public identifier. + * @param systemId The location of the DTD, as found in the XML document. + */ + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException { + InputStream inputStream = null; + + + try { + + /* Search the resource Map and (if not found) file Map */ + + String location = (String)resourceDtds.get(publicId); + if (location != null) { + inputStream + = ClassLoader.getSystemResource(location).openStream(); + } else { + location = (String)fileDtds.get(publicId); + if (location != null) { + inputStream = new FileInputStream(location); + } + } + } catch (IOException e) { + return super.resolveEntity(publicId, systemId); + } + + if (inputStream == null) { + return super.resolveEntity(publicId, systemId); + } else { + return new InputSource(inputStream); + } + } + + /** + * Receive notification that the start of an XML element has been found. + * + * @param name String name of the element found. + * @param atts AttributeList of the attributes included with the element + * (if any). + * @throws SAXException If the parser cannot process the document. + */ + public void startElement(String name, AttributeList atts) + throws SAXException { + + /* + * I need to "push" the element onto the String (currentLoc) which + * always represents the current location in the XML document. + */ + currentLoc += "\\" + name; + + /* A new element has started, so reset the text being captured */ + currentText = ""; + + if (currentLoc.equals("\\ejb-jar")) { + iasDescriptor = false; + } else if (currentLoc.equals("\\ias-ejb-jar")) { + iasDescriptor = true; + } + + if ((name.equals("session")) || (name.equals("entity"))) { + ejbType = name; + } + } + + /** + * Receive notification that character data has been found in the XML + * document + * + * @param ch Array of characters which have been found in the document. + * @param start Starting index of the data found in the document. + * @param len The number of characters found in the document. + * @throws SAXException If the parser cannot process the document. + */ + public void characters(char[] ch, int start, int len) + throws SAXException { + + currentText += new String(ch).substring(start, start + len); + } + + /** + * Receive notification that the end of an XML element has been found. + * + * @param name String name of the element. + * @throws SAXException If the parser cannot process the document. + */ + public void endElement(String name) throws SAXException { + + /* + * If this is a standard EJB 1.1 descriptor, we are looking for one + * set of data, while if this is an iAS-specific descriptor, we're + * looking for different set of data. Hand the processing off to + * the appropriate method. + */ + if (iasDescriptor) { + iasCharacters(currentText); + } else { + stdCharacters(currentText); + } + + /* + * I need to "pop" the element off the String (currentLoc) which + * always represents my current location in the XML document. + */ + + int nameLength = name.length() + 1; // Add one for the "\" + int locLength = currentLoc.length(); + + currentLoc = currentLoc.substring(0, locLength - nameLength); + } + + /** + * Receive notification that character data has been found in a standard + * EJB 1.1 descriptor. We're interested in retrieving the home + * interface, remote interface, implementation class, the type of bean, + * and if the bean uses CMP. + * + * @param value String data found in the XML document. + */ + private void stdCharacters(String value) { + + if (currentLoc.equals("\\ejb-jar\\display-name")) { + displayName = value; + return; + } + + String base = "\\ejb-jar\\enterprise-beans\\" + ejbType; + + if (currentLoc.equals(base + "\\ejb-name")) { + currentEjb = (EjbInfo) ejbs.get(value); + if (currentEjb == null) { + currentEjb = new EjbInfo(value); + ejbs.put(value, currentEjb); + } + } else if (currentLoc.equals(base + "\\home")) { + currentEjb.setHome(value); + } else if (currentLoc.equals(base + "\\remote")) { + currentEjb.setRemote(value); + } else if (currentLoc.equals(base + "\\ejb-class")) { + currentEjb.setImplementation(value); + } else if (currentLoc.equals(base + "\\session-type")) { + currentEjb.setBeantype(value); + } else if (currentLoc.equals(base + "\\persistence-type")) { + currentEjb.setCmp(value); + } + } + + /** + * Receive notification that character data has been found in an + * iAS-specific descriptor. We're interested in retrieving data + * indicating whether the bean must support RMI/IIOP access, whether + * the bean must provide highly available stubs and skeletons (in the + * case of stateful session beans), and if this bean uses additional + * CMP XML descriptors (in the case of entity beans with CMP). + * + * @param value String data found in the XML document. + */ + private void iasCharacters(String value) { + String base = "\\ias-ejb-jar\\enterprise-beans\\" + ejbType; + + if (currentLoc.equals(base + "\\ejb-name")) { + currentEjb = (EjbInfo) ejbs.get(value); + if (currentEjb == null) { + currentEjb = new EjbInfo(value); + ejbs.put(value, currentEjb); + } + } else if (currentLoc.equals(base + "\\iiop")) { + currentEjb.setIiop(value); + } else if (currentLoc.equals(base + "\\failover-required")) { + currentEjb.setHasession(value); + } else if (currentLoc.equals(base + "\\persistence-manager" + + "\\properties-file-location")) { + currentEjb.addCmpDescriptor(value); + } + } + } // End of EjbcHandler inner class + + + /** + * This inner class represents an EJB that will be compiled using ejbc. + * + * @author Greg Nelson + * greg@netscape.com + */ + private class EjbInfo { + private String name; // EJB's display name + private Classname home; // EJB's home interface name + private Classname remote; // EJB's remote interface name + private Classname implementation; // EJB's implementation class + private String beantype = "entity"; // or "stateful" or "stateless" + private boolean cmp = false; // Does this EJB support CMP? + private boolean iiop = false; // Does this EJB support IIOP? + private boolean hasession = false; // Does this EJB require failover? + private List cmpDescriptors = new ArrayList(); // CMP descriptor list + + /** + * Construct a new EJBInfo object with the given name. + * + * @param name The display name for the EJB. + */ + public EjbInfo(String name) { + this.name = name; + } + + /** + * Returns the display name of the EJB. If a display name has not been + * set, it returns the EJB implementation classname (if the + * implementation class is not set, it returns "[unnamed]"). + * + * @return The display name for the EJB. + */ + public String getName() { + if (name == null) { + if (implementation == null) { + return "[unnamed]"; + } else { + return implementation.getClassName(); + } + } + return name; + } + + /* + * Below are getter's and setter's for each of the instance variables. + * Note that (in addition to supporting setters with the same type as + * the instance variable) a setter is provided with takes a String + * argument -- this are provided so the XML document handler can set + * the EJB values using the Strings it parses. + */ + + public void setHome(String home) { + setHome(new Classname(home)); + } + + public void setHome(Classname home) { + this.home = home; + } + + public Classname getHome() { + return home; + } + + public void setRemote(String remote) { + setRemote(new Classname(remote)); + } + + public void setRemote(Classname remote) { + this.remote = remote; + } + + public Classname getRemote() { + return remote; + } + + public void setImplementation(String implementation) { + setImplementation(new Classname(implementation)); + } + + public void setImplementation(Classname implementation) { + this.implementation = implementation; + } + + public Classname getImplementation() { + return implementation; + } + + public void setBeantype(String beantype) { + this.beantype = beantype.toLowerCase(); + } + + public String getBeantype() { + return beantype; + } + + public void setCmp(boolean cmp) { + this.cmp = cmp; + } + + public void setCmp(String cmp) { + setCmp(cmp.equals("Container")); + } + + public boolean getCmp() { + return cmp; + } + + public void setIiop(boolean iiop) { + this.iiop = iiop; + } + + public void setIiop(String iiop) { + setIiop(iiop.equals("true")); + } + + public boolean getIiop() { + return iiop; + } + + public void setHasession(boolean hasession) { + this.hasession = hasession; + } + + public void setHasession(String hasession) { + setHasession(hasession.equals("true")); + } + + public boolean getHasession() { + return hasession; + } + + public void addCmpDescriptor(String descriptor) { + cmpDescriptors.add(descriptor); + } + + public List getCmpDescriptors() { + return cmpDescriptors; + } + + /** + * Verifies that the EJB is valid--if it is invalid, an exception is + * thrown + * + * + * @param buildDir The directory where the EJB remote interface, home + * interface, and implementation class must be found. + * @throws EjbcException If the EJB is invalid. + */ + private void checkConfiguration(File buildDir) throws EjbcException { + + /* Check that the specified instance variables are valid */ + if (home == null) { + throw new EjbcException("A home interface was not found " + + "for the " + name + " EJB."); + } + if (remote == null) { + throw new EjbcException("A remote interface was not found " + + "for the " + name + " EJB."); + } + if (implementation == null) { + throw new EjbcException("An EJB implementation class was not " + + "found for the " + name + " EJB."); + } + + if ((!beantype.equals(ENTITY_BEAN)) + && (!beantype.equals(STATELESS_SESSION)) + && (!beantype.equals(STATEFUL_SESSION))) { + throw new EjbcException("The beantype found (" + beantype + ") " + + "isn't valid in the " + name + " EJB."); + } + + if (cmp && (!beantype.equals(ENTITY_BEAN))) { + System.out.println("CMP stubs and skeletons may not be generated" + + " for a Session Bean -- the \"cmp\" attribute will be" + + " ignoredfor the " + name + " EJB."); + } + + if (hasession && (!beantype.equals(STATEFUL_SESSION))) { + System.out.println("Highly available stubs and skeletons may " + + "only be generated for a Stateful Session Bean -- the " + + "\"hasession\" attribute will be ignored for the " + + name + " EJB."); + } + + /* Check that the EJB "source" classes all exist */ + if (!remote.getClassFile(buildDir).exists()) { + throw new EjbcException("The remote interface " + + remote.getQualifiedClassName() + " could not be " + + "found."); + } + if (!home.getClassFile(buildDir).exists()) { + throw new EjbcException("The home interface " + + home.getQualifiedClassName() + " could not be " + + "found."); + } + if (!implementation.getClassFile(buildDir).exists()) { + throw new EjbcException("The EJB implementation class " + + implementation.getQualifiedClassName() + " could " + + "not be found."); + } + } + + /** + * Determines if the ejbc utility needs to be run or not. If the stubs + * and skeletons can all be found in the destination directory AND all + * of their timestamps are more recent than the EJB source classes + * (home, remote, and implementation classes), the method returns + * false. Otherwise, the method returns true. + * + * @param destDir The directory where the EJB source classes, stubs and + * skeletons are located. + * @return A boolean indicating whether or not the ejbc utility needs to + * be run to bring the stubs and skeletons up to date. + */ + public boolean mustBeRecompiled(File destDir) { + + long sourceModified = sourceClassesModified(destDir); + + long destModified = destClassesModified(destDir); + + return (destModified < sourceModified); + } + + /** + * Examines each of the EJB source classes (home, remote, and + * implementation) and returns the modification timestamp for the + * "oldest" class. + * + * @param classpath The classpath to be used to find the source EJB + * classes. If null, the system classpath + * is used. + * @return The modification timestamp for the "oldest" EJB source class. + * @throws BuildException If one of the EJB source classes cannot be + * found on the classpath. + */ + private long sourceClassesModified(File buildDir) { + long latestModified; // The timestamp of the "newest" class + long modified; // Timestamp for a given class + File remoteFile; // File for the remote interface class + File homeFile; // File for the home interface class + File implFile; // File for the EJB implementation class + + /* Check the timestamp on the remote interface */ + remoteFile = remote.getClassFile(buildDir); + modified = remoteFile.lastModified(); + if (modified == -1) { + System.out.println("The class " + + remote.getQualifiedClassName() + " couldn't " + + "be found on the classpath"); + return -1; + } + latestModified = modified; + + /* Check the timestamp on the home interface */ + homeFile = home.getClassFile(buildDir); + modified = homeFile.lastModified(); + if (modified == -1) { + System.out.println("The class " + + home.getQualifiedClassName() + " couldn't be " + + "found on the classpath"); + return -1; + } + latestModified = Math.max(latestModified, modified); + + /* Check the timestamp on the EJB implementation class. + * + * Note that if ONLY the implementation class has changed, it's not + * necessary to rebuild the EJB stubs and skeletons. For this + * reason, we ensure the file exists (using lastModified above), but + * we DON'T compare it's timestamp with the timestamps of the home + * and remote interfaces (because it's irrelevant in determining if + * ejbc must be run) + */ + implFile = implementation.getClassFile(buildDir); + modified = implFile.lastModified(); + if (modified == -1) { + System.out.println("The class " + + implementation.getQualifiedClassName() + + " couldn't be found on the classpath"); + return -1; + } + + String pathToFile = remote.getQualifiedClassName(); + pathToFile = pathToFile.replace('.', File.separatorChar) + ".class"; + ejbFiles.put(pathToFile, remoteFile); + + pathToFile = home.getQualifiedClassName(); + pathToFile = pathToFile.replace('.', File.separatorChar) + ".class"; + ejbFiles.put(pathToFile, homeFile); + + pathToFile = implementation.getQualifiedClassName(); + pathToFile = pathToFile.replace('.', File.separatorChar) + ".class"; + ejbFiles.put(pathToFile, implFile); + + return latestModified; + } + + /** + * Examines each of the EJB stubs and skeletons in the destination + * directory and returns the modification timestamp for the "oldest" + * class. If one of the stubs or skeletons cannot be found, -1 + * is returned. + * + * @param dest The directory in which the EJB stubs and skeletons are + * stored. + * @return The modification timestamp for the "oldest" EJB stub or + * skeleton. If one of the classes cannot be found, -1 + * is returned. + * @throws BuildException If the canonical path of the destination + * directory cannot be found. + */ + private long destClassesModified(File destDir) { + String[] classnames = classesToGenerate(); // List of all stubs & skels + long destClassesModified = new Date().getTime(); // Earliest mod time + boolean allClassesFound = true; // Has each been found? + + /* + * Loop through each stub/skeleton class that must be generated, and + * determine (if all exist) which file has the most recent timestamp + */ + for (int i=0; i < classnames.length; i++) { + + String pathToClass = + classnames[i].replace('.', File.separatorChar) + ".class"; + File classFile = new File(destDir, pathToClass); + + /* + * Add each stub/skeleton class to the list of EJB files. Note + * that each class is added even if it doesn't exist now. + */ + ejbFiles.put(pathToClass, classFile); + + allClassesFound = allClassesFound && classFile.exists(); + + if (allClassesFound) { + long fileMod = classFile.lastModified(); + + /* Keep track of the oldest modification timestamp */ + destClassesModified = Math.min(destClassesModified, fileMod); + } + } + + return (allClassesFound) ? destClassesModified : -1; + } + + /** + * Builds an array of class names which represent the stubs and + * skeletons which need to be generated for a given EJB. The class + * names are fully qualified. Nine classes are generated for all EJBs + * while an additional six classes are generated for beans requiring + * RMI/IIOP access. + * + * @return An array of Strings representing the fully-qualified class + * names for the stubs and skeletons to be generated. + */ + private String[] classesToGenerate() { + String[] classnames = (iiop) ? new String[15] : new String[9]; + + final String remotePkg = remote.getPackageName() + "."; + final String remoteClass = remote.getClassName(); + final String homePkg = home.getPackageName() + "."; + final String homeClass = home.getClassName(); + final String implPkg = implementation.getPackageName() + "."; + final String implFullClass = implementation.getQualifiedWithUnderscores(); + int index = 0; + + String fullPath; + + classnames[index++] = implPkg + "ejb_fac_" + implFullClass; + classnames[index++] = implPkg + "ejb_home_" + implFullClass; + classnames[index++] = implPkg + "ejb_skel_" + implFullClass; + classnames[index++] = remotePkg + "ejb_kcp_skel_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_skel_" + homeClass; + classnames[index++] = remotePkg + "ejb_kcp_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_stub_" + homeClass; + classnames[index++] = remotePkg + "ejb_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_stub_" + homeClass; + + if (!iiop) { + return classnames; + } + + classnames[index++] = remotePkg + "_" + remoteClass + "_Stub"; + classnames[index++] = homePkg + "_" + homeClass + "_Stub"; + classnames[index++] = remotePkg + "_ejb_RmiCorbaBridge_" + + remoteClass + "_Tie"; + classnames[index++] = homePkg + "_ejb_RmiCorbaBridge_" + homeClass + + "_Tie"; + classnames[index++] = remotePkg + "ejb_RmiCorbaBridge_" + + remoteClass; + classnames[index++] = homePkg + "ejb_RmiCorbaBridge_" + homeClass; + + return classnames; + } + + /** + * Convenience method which creates a String representation of all the + * instance variables of an EjbInfo object. + * + * @return A String representing the EjbInfo instance. + */ + public String toString() { + String s = "EJB name: " + name + + "\n\r home: " + home + + "\n\r remote: " + remote + + "\n\r impl: " + implementation + + "\n\r beantype: " + beantype + + "\n\r cmp: " + cmp + + "\n\r iiop: " + iiop + + "\n\r hasession: " + hasession; + + Iterator i = cmpDescriptors.iterator(); + while (i.hasNext()) { + s += "\n\r CMP Descriptor: " + i.next(); + } + + return s; + } + + } // End of EjbInfo inner class + + /** + * Convenience class used to represent the fully qualified name of a Java + * class. It provides an easy way to retrieve components of the class name + * in a format that is convenient for building iAS stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + */ + private class Classname { + private String qualifiedName; // Fully qualified name of the Java class + private String packageName; // Name of the package for this class + private String className; // Name of the class without the package + + /** + * This constructor builds an object which represents the name of a Java + * class. + * + * @param qualifiedName String representing the fully qualified class + * name of the Java class. + */ + public Classname(String qualifiedName) { + if (qualifiedName == null) { + return; + } + + this.qualifiedName = qualifiedName; + + int index = qualifiedName.lastIndexOf('.'); + if (index == -1) { + className = qualifiedName; + packageName = ""; + } else { + packageName = qualifiedName.substring(0, index); + className = qualifiedName.substring(index + 1); + } + } + + /** + * Gets the fully qualified name of the Java class. + * + * @return String representing the fully qualified class name. + */ + public String getQualifiedClassName() { + return qualifiedName; + } + + /** + * Gets the package name for the Java class. + * + * @return String representing the package name for the class. + */ + public String getPackageName() { + return packageName; + } + + /** + * Gets the Java class name without the package structure. + * + * @return String representing the name for the class. + */ + public String getClassName() { + return className; + } + + /** + * Gets the fully qualified name of the Java class with underscores + * separating the components of the class name rather than periods. + * This format is used in naming some of the stub and skeleton classes + * for the iPlanet Application Server. + * + * @return String representing the fully qualified class name using + * underscores instead of periods. + */ + public String getQualifiedWithUnderscores() { + return qualifiedName.replace('.', '_'); + } + + /** + * Returns a File which references the class relative to the specified + * directory. Note that the class file may or may not exist. + * + * @param directory A File referencing the base directory containing + * class files. + * @return File referencing this class. + */ + public File getClassFile(File directory) { + String pathToFile = qualifiedName.replace('.', File.separatorChar) + + ".class"; + return new File(directory, pathToFile); + } + + /** + * String representation of this class name. It returns the fully + * qualified class name. + * + * @return String representing the fully qualified class name. + */ + public String toString() { + return getQualifiedClassName(); + } + } // End of Classname inner class + + + /** + * Thread class used to redirect output from an InputStream to + * the JRE standard output. This class may be used to redirect output from + * an external process to the standard output. + * + * @author Greg Nelson greg@netscape.com + */ + private class RedirectOutput extends Thread { + InputStream stream; // Stream to read and redirect to standard output + + /** + * Constructs a new instance that will redirect output from the + * specified stream to the standard output. + * + * @param stream InputStream which will be read and redirected to the + * standard output. + */ + public RedirectOutput(InputStream stream) { + this.stream = stream; + } + + /** + * Reads text from the input stream and redirects it to standard output + * using a separate thread. + */ + public void run() { + BufferedReader reader = new BufferedReader( + new InputStreamReader(stream)); + String text; + try { + while ((text = reader.readLine()) != null) { + System.out.println(text); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + reader.close(); + } catch (IOException e) { + // Do nothing + } + } + } + } // End of RedirectOutput inner class + +} + \ No newline at end of file diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java new file mode 100644 index 000000000..494abcb44 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java @@ -0,0 +1,353 @@ +/* + * 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 + * . + */ + +package org.apache.tools.ant.taskdefs.optional.ejb; + +import java.io.File; +import java.io.IOException; + +import javax.xml.parsers.SAXParserFactory; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.ParserConfigurationException; +import org.xml.sax.SAXException; + +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.BuildException; + +/** + * Task to compile EJB stubs and skeletons for the iPlanet Application Server. + * The EJBs to be processed are specified by the EJB 1.1 standard XML + * descriptor, and additional attributes are obtained from the iPlanet Application + * Server-specific XML descriptor. Since the XML descriptors can include + * multiple EJBs, this is a convenient way of specifying many EJBs in a single + * Ant task. The following attributes are allowed: + *

    + *
  • ejbdescriptor -- Standard EJB 1.1 XML descriptor (typically + * titled "ejb-jar.xml"). This attribute is + * required. + *
  • iasdescriptor -- EJB XML descriptor for iPlanet Application + * Server (typically titled "ias-ejb-jar.xml). + * This attribute is required. + *
  • dest -- The is the base directory where the RMI stubs and + * skeletons are written. In addition, the class files + * for each bean (home interface, remote interface, and + * EJB implementation) must be found in this directory. + * This attribute is required. + *
  • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if + * omitted, the classpath specified when Ant was + * started will be used). Nested "classpath" + * elements may also be used. + *
  • keepgenerated -- Indicates whether or not the Java source + * files which are generated by ejbc will be + * saved or automatically deleted. If "yes", + * the source files will be retained. This is + * an optional attribute (if omitted, it + * defaults to "no"). + *
  • debug -- Indicates whether or not the ejbc utility should + * log additional debugging statements to the standard + * output. If "yes", the additional debugging statements + * will be generated (if omitted, it defaults to "no"). + *
  • iashome -- May be used to specify the "home" directory for + * this iPlanet Application Server installation. This + * is used to find the ejbc utility if it isn't + * included in the user's system path. This is an + * optional attribute (if specified, it should refer + * to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
+ *

+ * For each EJB specified, this task will locate the three classes that comprise + * the EJB. If these class files cannot be located in the dest + * directory, the task will fail. The task will also attempt to locate the EJB + * stubs and skeletons in this directory. If found, the timestamps on the + * stubs and skeletons will be checked to ensure they are up to date. Only if + * these files cannot be found or if they are out of date will ejbc be called + * to generate new stubs and skeletons. + * + * @see IPlanetEjbc + * @author Greg Nelson greg@netscape.com + */ +public class IPlanetEjbcTask extends Task { + + /* Attributes set by the Ant build file */ + private File ejbdescriptor; + private File iasdescriptor; + private File dest; + private Path classpath; + private boolean keepgenerated = false; + private boolean debug = false; + private File iashome; + + /** + * Sets the location of the standard XML EJB descriptor. Typically, this + * file is named "ejb-jar.xml". + * + * @param ejbdescriptor The name and location of the EJB descriptor. + */ + public void setEjbdescriptor(File ejbdescriptor) { + this.ejbdescriptor = ejbdescriptor; + } + + /** + * Sets the location of the iAS-specific XML EJB descriptor. Typically, + * this file is named "ias-ejb-jar.xml". + * + * @param iasdescriptor The name and location of the iAS-specific EJB + * descriptor. + */ + public void setIasdescriptor (File iasdescriptor) { + this.iasdescriptor = iasdescriptor; + } + + /** + * Sets the destination directory where the EJB "source" classes must exist + * and where the stubs and skeletons will be written. The destination + * directory must exist before this task is executed. + * + * @param dest The directory where the compiled classes will be written. + */ + public void setDest(File dest) { + this.dest = dest; + } + + /** + * Sets the classpath to be used when compiling the EJB stubs and skeletons. + * + * @param classpath The classpath to be used. + */ + public void setClasspath(Path classpath) { + if (this.classpath == null) { + this.classpath = classpath; + } else { + this.classpath.append(classpath); + } + } + + /** + * Creates a nested classpath element. + */ + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(project); + } + return classpath.createPath(); + } + + /** + * Sets whether or not the Java source files which are generated by the + * ejbc process should be retained or automatically deleted. + * + * @param keepgenerated A boolean indicating if the Java source files for + * the stubs and skeletons should be retained. + */ + public void setKeepgenerated(boolean keepgenerated) { + this.keepgenerated = keepgenerated; + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug(boolean debug) { + this.debug = debug; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be + * [install-location]/iplanet/ias6/ias. + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome(File iashome) { + this.iashome = iashome; + } + + /** + * Does the work. + */ + public void execute() throws BuildException { + checkConfiguration(); + + executeEjbc(getParser()); + } + + /** + * Verifies that the user selections are valid. + * + * @throws BuildException If the user selections are invalid. + */ + private void checkConfiguration() throws BuildException { + + if (ejbdescriptor == null) { + String msg = "The standard EJB descriptor must be specified using " + + "the \"ejbdescriptor\" attribute."; + throw new BuildException(msg, location); + } + if ((!ejbdescriptor.exists()) || (!ejbdescriptor.isFile())) { + String msg = "The standard EJB descriptor (" + ejbdescriptor + + ") was not found or isn't a file."; + throw new BuildException(msg, location); + } + + if (iasdescriptor == null) { + String msg = "The iAS-speific XML descriptor must be specified using" + + " the \"iasdescriptor\" attribute."; + throw new BuildException(msg, location); + } + if ((!iasdescriptor.exists()) || (!iasdescriptor.isFile())) { + String msg = "The iAS-specific XML descriptor (" + iasdescriptor + + ") was not found or isn't a file."; + throw new BuildException(msg, location); + } + + if (dest == null) { + String msg = "The destination directory must be specified using " + + "the \"dest\" attribute."; + throw new BuildException(msg, location); + } + if ((!dest.exists()) || (!dest.isDirectory())) { + String msg = "The destination directory (" + dest + ") was not " + + "found or isn't a directory."; + throw new BuildException(msg, location); + } + + if ((iashome != null) && (!iashome.isDirectory())) { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException(msg, getLocation()); + } + } + + /** + * Returns a SAXParser that may be used to process the XML descriptors. + * + * @return Parser which may be used to process the EJB descriptors. + * @throws BuildException If the parser cannot be created or configured. + */ + private SAXParser getParser() throws BuildException { + + SAXParser saxParser = null; + try { + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating(true); + saxParser = saxParserFactory.newSAXParser(); + } catch (SAXException e) { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException(msg, e, location); + } + catch (ParserConfigurationException e) { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException(msg, e, location); + } + + return saxParser; + } + + /** + * Executes the EJBc utility using the SAXParser provided. + * + * @param saxParser SAXParser that may be used to process the EJB + * descriptors + * @throws BuildException If there is an error reading or parsing the XML + * descriptors + */ + private void executeEjbc(SAXParser saxParser) throws BuildException { + IPlanetEjbc ejbc = new IPlanetEjbc(ejbdescriptor, + iasdescriptor, + dest, + getClasspath().toString(), + saxParser); + ejbc.setRetainSource(keepgenerated); + ejbc.setDebugOutput(debug); + if (iashome != null) { + ejbc.setIasHomeDir(iashome); + } + + try { + ejbc.execute(); + } catch (IOException e) { + String msg = "An IOException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException(msg, e, location); + } catch (SAXException e) { + String msg = "A SAXException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException(msg, e, location); + } catch (IPlanetEjbc.EjbcException e) { + String msg = "An exception occurred while trying to run the ejbc " + + "utility: " + e.getMessage(); + throw new BuildException(msg, e, location); + } + } + + /** + * Returns the CLASSPATH to be used when calling EJBc. If no user CLASSPATH + * is specified, the System classpath is returned instead. + * + * @return Path The classpath to be used for EJBc. + */ + private Path getClasspath() { + if (classpath == null) { + classpath = Path.systemClasspath; + } + + return classpath; + } +}