| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2000 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, if |
| * any, must include the following acknowlegement: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowlegement may appear in the software itself, |
| * if and wherever such third-party acknowlegements normally appear. |
| * |
| * 4. The names "The Jakarta Project", "Ant", and "Apache Software |
| * Foundation" must not be used to endorse or promote products derived |
| * from this software without prior written permission. For written |
| * permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache" |
| * nor may "Apache" appear in their names without prior written |
| * permission of the Apache Group. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| package org.apache.tools.ant.taskdefs.optional.ejb; |
| |
| import java.io.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. |
| * <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 |
| * @author Greg Nelson <a href="mailto:greg@netscape.com">greg@netscape.com</a> |
| */ |
| 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 |
| * <code>[install-location]/iplanet/ias6/ias</code>. |
| * |
| * @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 |
| * <a href="mailto:greg@netscape.com">greg@netscape.com</a> |
| */ |
| 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 EjbInfo |
| * @author Greg Nelson |
| * <a href="mailto:greg@netscape.com">greg@netscape.com</a> |
| */ |
| 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 <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 + "\\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 |
| * <a href="mailto:greg@netscape.com">greg@netscape.com</a> |
| */ |
| 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 |
| * <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 |
| |
| /* 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, <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[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 <a href="mailto:greg@netscape.com">greg@netscape.com</a> |
| */ |
| 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 <code>InputStream</code> to |
| * the JRE standard output. This class may be used to redirect output from |
| * an external process to the standard output. |
| * |
| * @author Greg Nelson <a href="mailto:greg@netscape.com">greg@netscape.com</a> |
| */ |
| 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 |
| |
| } |
| |