aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java')
-rw-r--r--framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java1495
1 files changed, 1495 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java
new file mode 100644
index 00000000..ed799d33
--- /dev/null
+++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java
@@ -0,0 +1,1495 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.tools.ant.taskdefs.optional.ejb;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.AttributeList;
+import org.xml.sax.HandlerBase;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * Compiles 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.
+ * <p>
+ * 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.
+ * <p>
+ * For more information on Ant Tasks for iPlanet Application Server, see the
+ * <code>IPlanetDeploymentTool</code> and <code>IPlanetEjbcTask</code> classes.
+ *
+ * @see IPlanetDeploymentTool
+ * @see IPlanetEjbcTask
+ * @ant.task ignore="true"
+ */
+public class IPlanetEjbc {
+
+ private static final int MIN_NUM_ARGS = 2;
+ private static final int MAX_NUM_ARGS = 8;
+ private static final int NUM_CLASSES_WITH_IIOP = 15;
+ private static final int NUM_CLASSES_WITHOUT_IIOP = 9;
+
+ /* 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.
+ * @todo classpathElements is not needed here, its never used
+ * (at least IDEA tells me so! :)
+ */
+ 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()]);
+ }
+ }
+
+ /**
+ * If true, the Java source files which are generated by the
+ * ejbc process are retained.
+ *
+ * @param retainSource A boolean indicating if the Java source files for
+ * the stubs and skeletons should be retained.
+ * @todo This is not documented in the HTML. On purpose?
+ */
+ public void setRetainSource(boolean retainSource) {
+ this.retainSource = retainSource;
+ }
+
+ /**
+ * If true, enables debugging output 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);
+ }
+
+ /**
+ * May be used to specify the "home" directory for this iAS installation.
+ * The directory specified should typically be
+ * <code>[install-location]/iplanet/ias6/ias</code>.
+ *
+ * @param iasHomeDir 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 < MIN_NUM_ARGS) || (args.length > MAX_NUM_ARGS)) {
+ 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.
+ *
+ */
+ 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 <code>getEjbs()</code> method.
+ *
+ * @see IPlanetEjbc.EjbInfo
+ */
+ private class EjbcHandler extends HandlerBase {
+ /** EJB 1.1 ID */
+ private static final String PUBLICID_EJB11 =
+ "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN";
+ /** IPlanet ID */
+ private static final String PUBLICID_IPLANET_EJB_60 =
+ "-//Sun Microsystems, Inc.//DTD iAS Enterprise JavaBeans 1.0//EN";
+ /** EJB 1.1 location */
+ private static final String DEFAULT_IAS60_EJB11_DTD_LOCATION =
+ "ejb-jar_1_1.dtd";
+ /** IAS60 location */
+ private static final String DEFAULT_IAS60_DTD_LOCATION =
+ "IASEjb_jar_1_0.dtd";
+
+ /*
+ * 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() {
+ 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 <code>
+ * InputSource</code> 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 + "\\prim-key-class")) {
+ currentEjb.setPrimaryKey(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.
+ *
+ */
+ 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 Classname primaryKey; // EJB's primary key 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 setPrimaryKey(String primaryKey) {
+ setPrimaryKey(new Classname(primaryKey));
+ }
+
+ public void setPrimaryKey(Classname primaryKey) {
+ this.primaryKey = primaryKey;
+ }
+
+ public Classname getPrimaryKey() {
+ return primaryKey;
+ }
+
+ 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
+ * <code>false</code>. Otherwise, the method returns <code>true</code>.
+ *
+ * @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 <code>null</code>, 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
+ File pkFile; // File for the EJB primary key 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 of the primary key class */
+ if (primaryKey != null) {
+ pkFile = primaryKey.getClassFile(buildDir);
+ modified = pkFile.lastModified();
+ if (modified == -1) {
+ System.out.println("The class "
+ + primaryKey.getQualifiedClassName() + "couldn't be "
+ + "found on the classpath");
+ return -1;
+ }
+ latestModified = Math.max(latestModified, modified);
+ } else {
+ pkFile = null;
+ }
+
+ /* 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);
+
+ if (pkFile != null) {
+ pathToFile = primaryKey.getQualifiedClassName();
+ pathToFile = pathToFile.replace('.', File.separatorChar) + ".class";
+ ejbFiles.put(pathToFile, pkFile);
+ }
+
+ 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, <code>-1
+ * </code> 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, <code>-1
+ * </code> 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[NUM_CLASSES_WITH_IIOP]
+ : new String[NUM_CLASSES_WITHOUT_IIOP];
+
+ 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;
+
+ 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++] = "org.omg.stub." + remotePkg + "_"
+ + remoteClass + "_Stub";
+ classnames[index++] = "org.omg.stub." + homePkg + "_"
+ + homeClass + "_Stub";
+ classnames[index++] = "org.omg.stub." + remotePkg
+ + "_ejb_RmiCorbaBridge_"
+ + remoteClass + "_Tie";
+ classnames[index++] = "org.omg.stub." + 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 primaryKey: " + primaryKey
+ + "\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.
+ *
+ */
+ private static 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 <code>InputStream</code> to
+ * the JRE standard output. This class may be used to redirect output from
+ * an external process to the standard output.
+ *
+ */
+ private static class RedirectOutput extends Thread {
+
+ private 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
+
+}