/* * 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.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.time.Instant; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; 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 java.util.stream.Collectors; import java.util.stream.Stream; 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; import org.apache.tools.ant.util.StringUtils; /** * 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<String, File> 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. */ if (classpath != null) { StringTokenizer st = new StringTokenizer(classpath, File.pathSeparator); final int count = st.countTokens(); classpathElements = Collections.list(st).toArray(new String[count]); } } /** * 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<String, File> 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() { return Stream.of(handler.getEjbs()).map(EjbInfo::getCmpDescriptors) .flatMap(Collection::stream).toArray(String[]::new); } /** * 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 ("-classpath".equals(args[i])) { classpath = args[++i]; } else if ("-d".equals(args[i])) { destDirectory = new File(args[++i]); } else if ("-debug".equals(args[i])) { debug = true; } else if ("-keepsource".equals(args[i])) { 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(); //NOSONAR 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 (EjbInfo ejb : ejbs) { log("EJBInfo..."); log(ejb.toString()); } for (EjbInfo ejb : ejbs) { ejb.checkConfiguration(destDirectory); // Throws EjbcException if (ejb.mustBeRecompiled(destDirectory)) { log(ejb.getName() + " must be recompiled using ejbc."); callEjbc(buildArgumentList(ejb)); } 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) { /* 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 "; /* Concatenate all of the command line arguments into a single String */ String args = Stream.of(arguments).collect(Collectors.joining(" ")); 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."); log(StringUtils.getStackTrace(e)); } 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 { /* * 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); return handler.getEjbs(); } /** * 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<String> 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 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 { private static final long serialVersionUID = 1L; /** * 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<String, String> resourceDtds = new HashMap<>(); private Map<String, String> fileDtds = new HashMap<>(); private Map<String, EjbInfo> 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 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. */ @Override 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 = resourceDtds.get(publicId); if (location != null) { inputStream = ClassLoader.getSystemResource(location).openStream(); } else { location = fileDtds.get(publicId); if (location != null) { // closed when the InputSource is closed inputStream = Files.newInputStream(Paths.get(location)); //NOSONAR } } } catch (IOException e) { } if (inputStream == null) { return super.resolveEntity(publicId, systemId); } 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. */ @Override 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 ("\\ejb-jar".equals(currentLoc)) { iasDescriptor = false; } else if ("\\ias-ejb-jar".equals(currentLoc)) { iasDescriptor = true; } if (("session".equals(name)) || ("entity".equals(name))) { 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. */ @Override 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. */ @Override 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 ("\\ejb-jar\\display-name".equals(currentLoc)) { displayName = value; return; } String base = "\\ejb-jar\\enterprise-beans\\" + ejbType; if ((base + "\\ejb-name").equals(currentLoc)) { currentEjb = ejbs.get(value); if (currentEjb == null) { currentEjb = new EjbInfo(value); ejbs.put(value, currentEjb); } } else if ((base + "\\home").equals(currentLoc)) { currentEjb.setHome(value); } else if ((base + "\\remote").equals(currentLoc)) { currentEjb.setRemote(value); } else if ((base + "\\ejb-class").equals(currentLoc)) { currentEjb.setImplementation(value); } else if ((base + "\\prim-key-class").equals(currentLoc)) { currentEjb.setPrimaryKey(value); } else if ((base + "\\session-type").equals(currentLoc)) { currentEjb.setBeantype(value); } else if ((base + "\\persistence-type").equals(currentLoc)) { 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 ((base + "\\ejb-name").equals(currentLoc)) { currentEjb = ejbs.get(value); if (currentEjb == null) { currentEjb = new EjbInfo(value); ejbs.put(value, currentEjb); } } else if ((base + "\\iiop").equals(currentLoc)) { currentEjb.setIiop(value); } else if ((base + "\\failover-required").equals(currentLoc)) { currentEjb.setHasession(value); } else if ((base + "\\persistence-manager\\properties-file-location") .equals(currentLoc)) { 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<String> 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]"; } 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("Container".equals(cmp)); } public boolean getCmp() { return cmp; } public void setIiop(boolean iiop) { this.iiop = iiop; } public void setIiop(String iiop) { setIiop(Boolean.parseBoolean(iiop)); } public boolean getIiop() { return iiop; } public void setHasession(boolean hasession) { this.hasession = hasession; } public void setHasession(String hasession) { setHasession(Boolean.parseBoolean(hasession)); } public boolean getHasession() { return hasession; } public void addCmpDescriptor(String descriptor) { cmpDescriptors.add(descriptor); } public List<String> 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 = Instant.now().toEpochMilli(); // 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. */ @Override 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<String> 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. */ @Override 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. */ @Override public void run() { try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { String text; while ((text = reader.readLine()) != null) { System.out.println(text); } } catch (IOException e) { e.printStackTrace(); //NOSONAR } } } // End of RedirectOutput inner class }