1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.ejb;
  18. import java.io.BufferedReader;
  19. import java.io.File;
  20. import java.io.FileInputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.InputStreamReader;
  24. import java.util.ArrayList;
  25. import java.util.Date;
  26. import java.util.HashMap;
  27. import java.util.Hashtable;
  28. import java.util.Iterator;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Properties;
  32. import java.util.StringTokenizer;
  33. import javax.xml.parsers.SAXParser;
  34. import javax.xml.parsers.SAXParserFactory;
  35. import org.xml.sax.AttributeList;
  36. import org.xml.sax.HandlerBase;
  37. import org.xml.sax.InputSource;
  38. import org.xml.sax.SAXException;
  39. /**
  40. * Compiles EJB stubs and skeletons for the iPlanet Application
  41. * Server (iAS). The class will read a standard EJB descriptor (as well as an
  42. * EJB descriptor specific to iPlanet Application Server) to identify one or
  43. * more EJBs to process. It will search for EJB "source" classes (the remote
  44. ; * interface, home interface, and EJB implementation class) and the EJB stubs
  45. * and skeletons in the specified destination directory. Only if the stubs and
  46. * skeletons cannot be found or if they're out of date will the iPlanet
  47. * Application Server ejbc utility be run.
  48. * <p>
  49. * Because this class (and it's assorted inner classes) may be bundled into the
  50. * iPlanet Application Server distribution at some point (and removed from the
  51. * Ant distribution), the class has been written to be independent of all
  52. * Ant-specific classes. It is also for this reason (and to avoid cluttering
  53. * the Apache Ant source files) that this utility has been packaged into a
  54. * single source file.
  55. * <p>
  56. * For more information on Ant Tasks for iPlanet Application Server, see the
  57. * <code>IPlanetDeploymentTool</code> and <code>IPlanetEjbcTask</code> classes.
  58. *
  59. * @see IPlanetDeploymentTool
  60. * @see IPlanetEjbcTask
  61. * @ant.task ignore="true"
  62. */
  63. public class IPlanetEjbc {
  64. /* Constants used for the "beantype" attribute */
  65. private static final String ENTITY_BEAN = "entity";
  66. private static final String STATELESS_SESSION = "stateless";
  67. private static final String STATEFUL_SESSION = "stateful";
  68. /* Filenames of the standard EJB descriptor and the iAS-specific descriptor */
  69. private File stdDescriptor;
  70. private File iasDescriptor;
  71. /*
  72. * Directory where "source" EJB files are stored and where stubs and
  73. * skeletons will also be written.
  74. */
  75. private File destDirectory;
  76. /* Classpath used when the iAS ejbc is called */
  77. private String classpath;
  78. private String[] classpathElements;
  79. /* Options passed to the iAS ejbc */
  80. private boolean retainSource = false;
  81. private boolean debugOutput = false;
  82. /* iAS installation directory (used if ejbc isn't on user's PATH) */
  83. private File iasHomeDir;
  84. /* Parser and handler used to process both EJB descriptor files */
  85. private SAXParser parser;
  86. private EjbcHandler handler = new EjbcHandler();
  87. /*
  88. * This Hashtable maintains a list of EJB class files processed by the ejbc
  89. * utility (both "source" class files as well as stubs and skeletons). The
  90. * key for the Hashtable is a String representing the path to the class file
  91. * (relative to the destination directory). The value for the Hashtable is
  92. * a File object which reference the actual class file.
  93. */
  94. private Hashtable ejbFiles = new Hashtable();
  95. /* Value of the display-name element read from the standard EJB descriptor */
  96. private String displayName;
  97. /**
  98. * Constructs an instance which may be used to process EJB descriptors and
  99. * generate EJB stubs and skeletons, if needed.
  100. *
  101. * @param stdDescriptor File referencing a standard EJB descriptor.
  102. * @param iasDescriptor File referencing an iAS-specific EJB descriptor.
  103. * @param destDirectory File referencing the base directory where both
  104. * EJB "source" files are found and where stubs and
  105. * skeletons will be written.
  106. * @param classpath String representation of the classpath to be used
  107. * by the iAS ejbc utility.
  108. * @param parser SAXParser to be used to process both of the EJB
  109. * descriptors.
  110. * @todo classpathElements is not needed here, its never used
  111. * (at least IDEA tells me so! :)
  112. */
  113. public IPlanetEjbc(File stdDescriptor,
  114. File iasDescriptor,
  115. File destDirectory,
  116. String classpath,
  117. SAXParser parser) {
  118. this.stdDescriptor = stdDescriptor;
  119. this.iasDescriptor = iasDescriptor;
  120. this.destDirectory = destDirectory;
  121. this.classpath = classpath;
  122. this.parser = parser;
  123. /*
  124. * Parse the classpath into it's individual elements and store the
  125. * results in the "classpathElements" instance variable.
  126. */
  127. List elements = new ArrayList();
  128. if (classpath != null) {
  129. StringTokenizer st = new StringTokenizer(classpath,
  130. File.pathSeparator);
  131. while (st.hasMoreTokens()) {
  132. elements.add(st.nextToken());
  133. }
  134. classpathElements
  135. = (String[]) elements.toArray(new String[elements.size()]);
  136. }
  137. }
  138. /**
  139. * If true, the Java source files which are generated by the
  140. * ejbc process are retained.
  141. *
  142. * @param retainSource A boolean indicating if the Java source files for
  143. * the stubs and skeletons should be retained.
  144. * @todo This is not documented in the HTML. On purpose?
  145. */
  146. public void setRetainSource(boolean retainSource) {
  147. this.retainSource = retainSource;
  148. }
  149. /**
  150. * If true, enables debugging output when ejbc is executed.
  151. *
  152. * @param debugOutput A boolean indicating if debugging output should be
  153. * generated
  154. */
  155. public void setDebugOutput(boolean debugOutput) {
  156. this.debugOutput = debugOutput;
  157. }
  158. /**
  159. * Registers the location of a local DTD file or resource. By registering
  160. * a local DTD, EJB descriptors can be parsed even when the remote servers
  161. * which contain the "public" DTDs cannot be accessed.
  162. *
  163. * @param publicID The public DTD identifier found in an XML document.
  164. * @param location The file or resource name for the appropriate DTD stored
  165. * on the local machine.
  166. */
  167. public void registerDTD(String publicID, String location) {
  168. handler.registerDTD(publicID, location);
  169. }
  170. /**
  171. * May be used to specify the "home" directory for this iAS installation.
  172. * The directory specified should typically be
  173. * <code>[install-location]/iplanet/ias6/ias</code>.
  174. *
  175. * @param iasHomeDir The home directory for the user's iAS installation.
  176. */
  177. public void setIasHomeDir(File iasHomeDir) {
  178. this.iasHomeDir = iasHomeDir;
  179. }
  180. /**
  181. * Returns a Hashtable which contains a list of EJB class files processed by
  182. * the ejbc utility (both "source" class files as well as stubs and
  183. * skeletons). The key for the Hashtable is a String representing the path
  184. * to the class file (relative to the destination directory). The value for
  185. * the Hashtable is a File object which reference the actual class file.
  186. *
  187. * @return The list of EJB files processed by the ejbc utility.
  188. */
  189. public Hashtable getEjbFiles() {
  190. return ejbFiles;
  191. }
  192. /**
  193. * Returns the display-name element read from the standard EJB descriptor.
  194. *
  195. * @return The EJB-JAR display name.
  196. */
  197. public String getDisplayName() {
  198. return displayName;
  199. }
  200. /**
  201. * Returns the list of CMP descriptors referenced in the EJB descriptors.
  202. *
  203. * @return An array of CMP descriptors.
  204. */
  205. public String[] getCmpDescriptors() {
  206. List returnList = new ArrayList();
  207. EjbInfo[] ejbs = handler.getEjbs();
  208. for (int i = 0; i < ejbs.length; i++) {
  209. List descriptors = (List) ejbs[i].getCmpDescriptors();
  210. returnList.addAll(descriptors);
  211. }
  212. return (String[]) returnList.toArray(new String[returnList.size()]);
  213. }
  214. /**
  215. * Main application method for the iPlanet Application Server ejbc utility.
  216. * If the application is run with no commandline arguments, a usage
  217. * statement is printed for the user.
  218. *
  219. * @param args The commandline arguments passed to the application.
  220. */
  221. public static void main(String[] args) {
  222. File stdDescriptor;
  223. File iasDescriptor;
  224. File destDirectory = null;
  225. String classpath = null;
  226. SAXParser parser = null;
  227. boolean debug = false;
  228. boolean retainSource = false;
  229. IPlanetEjbc ejbc;
  230. if ((args.length < 2) || (args.length > 8)) {
  231. usage();
  232. return;
  233. }
  234. stdDescriptor = new File(args[args.length - 2]);
  235. iasDescriptor = new File(args[args.length - 1]);
  236. for (int i = 0; i < args.length - 2; i++) {
  237. if (args[i].equals("-classpath")) {
  238. classpath = args[++i];
  239. } else if (args[i].equals("-d")) {
  240. destDirectory = new File(args[++i]);
  241. } else if (args[i].equals("-debug")) {
  242. debug = true;
  243. } else if (args[i].equals("-keepsource")) {
  244. retainSource = true;
  245. } else {
  246. usage();
  247. return;
  248. }
  249. }
  250. /* If the -classpath flag isn't specified, use the system classpath */
  251. if (classpath == null) {
  252. Properties props = System.getProperties();
  253. classpath = props.getProperty("java.class.path");
  254. }
  255. /*
  256. * If the -d flag isn't specified, use the working directory as the
  257. * destination directory
  258. */
  259. if (destDirectory == null) {
  260. Properties props = System.getProperties();
  261. destDirectory = new File(props.getProperty("user.dir"));
  262. }
  263. /* Construct a SAXParser used to process the descriptors */
  264. SAXParserFactory parserFactory = SAXParserFactory.newInstance();
  265. parserFactory.setValidating(true);
  266. try {
  267. parser = parserFactory.newSAXParser();
  268. } catch (Exception e) {
  269. // SAXException or ParserConfigurationException may be thrown
  270. System.out.println("An exception was generated while trying to ");
  271. System.out.println("create a new SAXParser.");
  272. e.printStackTrace();
  273. return;
  274. }
  275. /* Build and populate an instance of the ejbc utility */
  276. ejbc = new IPlanetEjbc(stdDescriptor, iasDescriptor, destDirectory,
  277. classpath, parser);
  278. ejbc.setDebugOutput(debug);
  279. ejbc.setRetainSource(retainSource);
  280. /* Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed */
  281. try {
  282. ejbc.execute();
  283. } catch (IOException e) {
  284. System.out.println("An IOException has occurred while reading the "
  285. + "XML descriptors (" + e.getMessage() + ").");
  286. return;
  287. } catch (SAXException e) {
  288. System.out.println("A SAXException has occurred while reading the "
  289. + "XML descriptors (" + e.getMessage() + ").");
  290. return;
  291. } catch (IPlanetEjbc.EjbcException e) {
  292. System.out.println("An error has occurred while executing the ejbc "
  293. + "utility (" + e.getMessage() + ").");
  294. return;
  295. }
  296. }
  297. /**
  298. * Print a usage statement.
  299. */
  300. private static void usage() {
  301. System.out.println("java org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbc \\");
  302. System.out.println(" [OPTIONS] [EJB 1.1 descriptor] [iAS EJB descriptor]");
  303. System.out.println("");
  304. System.out.println("Where OPTIONS are:");
  305. System.out.println(" -debug -- for additional debugging output");
  306. System.out.println(" -keepsource -- to retain Java source files generated");
  307. System.out.println(" -classpath [classpath] -- classpath used for compilation");
  308. System.out.println(" -d [destination directory] -- directory for compiled classes");
  309. System.out.println("");
  310. System.out.println("If a classpath is not specified, the system classpath");
  311. System.out.println("will be used. If a destination directory is not specified,");
  312. System.out.println("the current working directory will be used (classes will");
  313. System.out.println("still be placed in subfolders which correspond to their");
  314. System.out.println("package name).");
  315. System.out.println("");
  316. System.out.println("The EJB home interface, remote interface, and implementation");
  317. System.out.println("class must be found in the destination directory. In");
  318. System.out.println("addition, the destination will look for the stubs and skeletons");
  319. System.out.println("in the destination directory to ensure they are up to date.");
  320. }
  321. /**
  322. * Compiles the stub and skeletons for the specified EJBs, if they need to
  323. * be updated.
  324. *
  325. * @throws EjbcException If the ejbc utility cannot be correctly configured
  326. * or if one or more of the EJB "source" classes
  327. * cannot be found in the destination directory
  328. * @throws IOException If the parser encounters a problem reading the XML
  329. * file
  330. * @throws SAXException If the parser encounters a problem processing the
  331. * XML descriptor (it may wrap another exception)
  332. */
  333. public void execute() throws EjbcException, IOException, SAXException {
  334. checkConfiguration(); // Throws EjbcException if unsuccessful
  335. EjbInfo[] ejbs = getEjbs(); // Returns list of EJBs for processing
  336. for (int i = 0; i < ejbs.length; i++) {
  337. log("EJBInfo...");
  338. log(ejbs[i].toString());
  339. }
  340. for (int i = 0; i < ejbs.length; i++) {
  341. EjbInfo ejb = ejbs[i];
  342. ejb.checkConfiguration(destDirectory); // Throws EjbcException
  343. if (ejb.mustBeRecompiled(destDirectory)) {
  344. log(ejb.getName() + " must be recompiled using ejbc.");
  345. String[] arguments = buildArgumentList(ejb);
  346. callEjbc(arguments);
  347. } else {
  348. log(ejb.getName() + " is up to date.");
  349. }
  350. }
  351. }
  352. /**
  353. * Executes the iPlanet Application Server ejbc command-line utility.
  354. *
  355. * @param arguments Command line arguments to be passed to the ejbc utility.
  356. */
  357. private void callEjbc(String[] arguments) {
  358. /* Concatenate all of the command line arguments into a single String */
  359. StringBuffer args = new StringBuffer();
  360. for (int i = 0; i < arguments.length; i++) {
  361. args.append(arguments[i]).append(" ");
  362. }
  363. /* If an iAS home directory is specified, prepend it to the commmand */
  364. String command;
  365. if (iasHomeDir == null) {
  366. command = "";
  367. } else {
  368. command = iasHomeDir.toString() + File.separator + "bin"
  369. + File.separator;
  370. }
  371. command += "ejbc ";
  372. log(command + args);
  373. /*
  374. * Use the Runtime object to execute an external command. Use the
  375. * RedirectOutput inner class to direct the standard and error output
  376. * from the command to the JRE's standard output
  377. */
  378. try {
  379. Process p = Runtime.getRuntime().exec(command + args);
  380. RedirectOutput output = new RedirectOutput(p.getInputStream());
  381. RedirectOutput error = new RedirectOutput(p.getErrorStream());
  382. output.start();
  383. error.start();
  384. p.waitFor();
  385. p.destroy();
  386. } catch (IOException e) {
  387. log("An IOException has occurred while trying to execute ejbc.");
  388. e.printStackTrace();
  389. } catch (InterruptedException e) {
  390. // Do nothing
  391. }
  392. }
  393. /**
  394. * Verifies that the user selections are valid.
  395. *
  396. * @throws EjbcException If the user selections are invalid.
  397. */
  398. protected void checkConfiguration() throws EjbcException {
  399. String msg = "";
  400. if (stdDescriptor == null) {
  401. msg += "A standard XML descriptor file must be specified. ";
  402. }
  403. if (iasDescriptor == null) {
  404. msg += "An iAS-specific XML descriptor file must be specified. ";
  405. }
  406. if (classpath == null) {
  407. msg += "A classpath must be specified. ";
  408. }
  409. if (parser == null) {
  410. msg += "An XML parser must be specified. ";
  411. }
  412. if (destDirectory == null) {
  413. msg += "A destination directory must be specified. ";
  414. } else if (!destDirectory.exists()) {
  415. msg += "The destination directory specified does not exist. ";
  416. } else if (!destDirectory.isDirectory()) {
  417. msg += "The destination specified is not a directory. ";
  418. }
  419. if (msg.length() > 0) {
  420. throw new EjbcException(msg);
  421. }
  422. }
  423. /**
  424. * Parses the EJB descriptors and returns a list of EJBs which may need to
  425. * be compiled.
  426. *
  427. * @return An array of objects which describe the EJBs to be
  428. * processed.
  429. * @throws IOException If the parser encounters a problem reading the XML
  430. * files
  431. * @throws SAXException If the parser encounters a problem processing the
  432. * XML descriptor (it may wrap another exception)
  433. */
  434. private EjbInfo[] getEjbs() throws IOException, SAXException {
  435. EjbInfo[] ejbs = null;
  436. /*
  437. * The EJB information is gathered from the standard XML EJB descriptor
  438. * and the iAS-specific XML EJB descriptor using a SAX parser.
  439. */
  440. parser.parse(stdDescriptor, handler);
  441. parser.parse(iasDescriptor, handler);
  442. ejbs = handler.getEjbs();
  443. return ejbs;
  444. }
  445. /**
  446. * Based on this object's instance variables as well as the EJB to be
  447. * processed, the correct flags and parameters are set for the ejbc
  448. * command-line utility.
  449. * @param ejb The EJB for which stubs and skeletons will be compiled.
  450. * @return An array of Strings which are the command-line parameters for
  451. * for the ejbc utility.
  452. */
  453. private String[] buildArgumentList(EjbInfo ejb) {
  454. List arguments = new ArrayList();
  455. /* OPTIONAL COMMAND LINE PARAMETERS */
  456. if (debugOutput) {
  457. arguments.add("-debug");
  458. }
  459. /* No beantype flag is needed for an entity bean */
  460. if (ejb.getBeantype().equals(STATELESS_SESSION)) {
  461. arguments.add("-sl");
  462. } else if (ejb.getBeantype().equals(STATEFUL_SESSION)) {
  463. arguments.add("-sf");
  464. }
  465. if (ejb.getIiop()) {
  466. arguments.add("-iiop");
  467. }
  468. if (ejb.getCmp()) {
  469. arguments.add("-cmp");
  470. }
  471. if (retainSource) {
  472. arguments.add("-gs");
  473. }
  474. if (ejb.getHasession()) {
  475. arguments.add("-fo");
  476. }
  477. /* REQUIRED COMMAND LINE PARAMETERS */
  478. arguments.add("-classpath");
  479. arguments.add(classpath);
  480. arguments.add("-d");
  481. arguments.add(destDirectory.toString());
  482. arguments.add(ejb.getHome().getQualifiedClassName());
  483. arguments.add(ejb.getRemote().getQualifiedClassName());
  484. arguments.add(ejb.getImplementation().getQualifiedClassName());
  485. /* Convert the List into an Array and return it */
  486. return (String[]) arguments.toArray(new String[arguments.size()]);
  487. }
  488. /**
  489. * Convenience method used to print messages to the user if debugging
  490. * messages are enabled.
  491. *
  492. * @param msg The String to print to standard output.
  493. */
  494. private void log(String msg) {
  495. if (debugOutput) {
  496. System.out.println(msg);
  497. }
  498. }
  499. /* Inner classes follow */
  500. /**
  501. * This inner class is used to signal any problems during the execution of
  502. * the ejbc compiler.
  503. *
  504. */
  505. public class EjbcException extends Exception {
  506. /**
  507. * Constructs an exception with the given descriptive message.
  508. *
  509. * @param msg Description of the exception which has occurred.
  510. */
  511. public EjbcException(String msg) {
  512. super(msg);
  513. }
  514. } // End of EjbcException inner class
  515. /**
  516. * This inner class is an XML document handler that can be used to parse EJB
  517. * descriptors (both the standard EJB descriptor as well as the iAS-specific
  518. * descriptor that stores additional values for iAS). Once the descriptors
  519. * have been processed, the list of EJBs found can be obtained by calling
  520. * the <code>getEjbs()</code> method.
  521. *
  522. * @see IPlanetEjbc.EjbInfo
  523. */
  524. private class EjbcHandler extends HandlerBase {
  525. /*
  526. * Two Maps are used to track local DTDs that will be used in case the
  527. * remote copies of these DTDs cannot be accessed. The key for the Map
  528. * is the DTDs public ID and the value is the local location for the DTD
  529. */
  530. private Map resourceDtds = new HashMap();
  531. private Map fileDtds = new HashMap();
  532. private Map ejbs = new HashMap(); // List of EJBs found in XML
  533. private EjbInfo currentEjb; // One item within the Map
  534. private boolean iasDescriptor = false; // Is doc iAS or EJB descriptor
  535. private String currentLoc = ""; // Tracks current element
  536. private String currentText; // Tracks current text data
  537. private String ejbType; // "session" or "entity"
  538. /**
  539. * Constructs a new instance of the handler and registers local copies
  540. * of the standard EJB 1.1 descriptor DTD as well as iAS's EJB
  541. * descriptor DTD.
  542. */
  543. public EjbcHandler() {
  544. final String PUBLICID_EJB11 =
  545. "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN";
  546. final String PUBLICID_IPLANET_EJB_60 =
  547. "-//Sun Microsystems, Inc.//DTD iAS Enterprise JavaBeans 1.0//EN";
  548. final String DEFAULT_IAS60_EJB11_DTD_LOCATION =
  549. "ejb-jar_1_1.dtd";
  550. final String DEFAULT_IAS60_DTD_LOCATION =
  551. "IASEjb_jar_1_0.dtd";
  552. registerDTD(PUBLICID_EJB11, DEFAULT_IAS60_EJB11_DTD_LOCATION);
  553. registerDTD(PUBLICID_IPLANET_EJB_60, DEFAULT_IAS60_DTD_LOCATION);
  554. }
  555. /**
  556. * Returns the list of EJB objects found during the processing of the
  557. * standard EJB 1.1 descriptor and iAS-specific EJB descriptor.
  558. *
  559. * @return An array of EJBs which were found during the descriptor
  560. * parsing.
  561. */
  562. public EjbInfo[] getEjbs() {
  563. return (EjbInfo[]) ejbs.values().toArray(new EjbInfo[ejbs.size()]);
  564. }
  565. /**
  566. * Returns the value of the display-name element found in the standard
  567. * EJB 1.1 descriptor.
  568. *
  569. * @return String display-name value.
  570. */
  571. public String getDisplayName() {
  572. return displayName;
  573. }
  574. /**
  575. * Registers a local DTD that will be used when parsing an EJB
  576. * descriptor. When the DTD's public identifier is found in an XML
  577. * document, the parser will reference the local DTD rather than the
  578. * remote DTD. This enables XML documents to be processed even when the
  579. * public DTD isn't available.
  580. *
  581. * @param publicID The DTD's public identifier.
  582. * @param location The location of the local DTD copy -- the location
  583. * may either be a resource found on the classpath or a
  584. * local file.
  585. */
  586. public void registerDTD(String publicID, String location) {
  587. log("Registering: " + location);
  588. if ((publicID == null) || (location == null)) {
  589. return;
  590. }
  591. if (ClassLoader.getSystemResource(location) != null) {
  592. log("Found resource: " + location);
  593. resourceDtds.put(publicID, location);
  594. } else {
  595. File dtdFile = new File(location);
  596. if (dtdFile.exists() && dtdFile.isFile()) {
  597. log("Found file: " + location);
  598. fileDtds.put(publicID, location);
  599. }
  600. }
  601. }
  602. /**
  603. * Resolves an external entity found during XML processing. If a public
  604. * ID is found that has been registered with the handler, an <code>
  605. * InputSource</code> will be returned which refers to the local copy.
  606. * If the public ID hasn't been registered or if an error occurs, the
  607. * superclass implementation is used.
  608. *
  609. * @param publicId The DTD's public identifier.
  610. * @param systemId The location of the DTD, as found in the XML document.
  611. */
  612. public InputSource resolveEntity(String publicId, String systemId)
  613. throws SAXException {
  614. InputStream inputStream = null;
  615. try {
  616. /* Search the resource Map and (if not found) file Map */
  617. String location = (String) resourceDtds.get(publicId);
  618. if (location != null) {
  619. inputStream
  620. = ClassLoader.getSystemResource(location).openStream();
  621. } else {
  622. location = (String) fileDtds.get(publicId);
  623. if (location != null) {
  624. inputStream = new FileInputStream(location);
  625. }
  626. }
  627. } catch (IOException e) {
  628. return super.resolveEntity(publicId, systemId);
  629. }
  630. if (inputStream == null) {
  631. return super.resolveEntity(publicId, systemId);
  632. } else {
  633. return new InputSource(inputStream);
  634. }
  635. }
  636. /**
  637. * Receive notification that the start of an XML element has been found.
  638. *
  639. * @param name String name of the element found.
  640. * @param atts AttributeList of the attributes included with the element
  641. * (if any).
  642. * @throws SAXException If the parser cannot process the document.
  643. */
  644. public void startElement(String name, AttributeList atts)
  645. throws SAXException {
  646. /*
  647. * I need to "push" the element onto the String (currentLoc) which
  648. * always represents the current location in the XML document.
  649. */
  650. currentLoc += "\\" + name;
  651. /* A new element has started, so reset the text being captured */
  652. currentText = "";
  653. if (currentLoc.equals("\\ejb-jar")) {
  654. iasDescriptor = false;
  655. } else if (currentLoc.equals("\\ias-ejb-jar")) {
  656. iasDescriptor = true;
  657. }
  658. if ((name.equals("session")) || (name.equals("entity"))) {
  659. ejbType = name;
  660. }
  661. }
  662. /**
  663. * Receive notification that character data has been found in the XML
  664. * document
  665. *
  666. * @param ch Array of characters which have been found in the document.
  667. * @param start Starting index of the data found in the document.
  668. * @param len The number of characters found in the document.
  669. * @throws SAXException If the parser cannot process the document.
  670. */
  671. public void characters(char[] ch, int start, int len)
  672. throws SAXException {
  673. currentText += new String(ch).substring(start, start + len);
  674. }
  675. /**
  676. * Receive notification that the end of an XML element has been found.
  677. *
  678. * @param name String name of the element.
  679. * @throws SAXException If the parser cannot process the document.
  680. */
  681. public void endElement(String name) throws SAXException {
  682. /*
  683. * If this is a standard EJB 1.1 descriptor, we are looking for one
  684. * set of data, while if this is an iAS-specific descriptor, we're
  685. * looking for different set of data. Hand the processing off to
  686. * the appropriate method.
  687. */
  688. if (iasDescriptor) {
  689. iasCharacters(currentText);
  690. } else {
  691. stdCharacters(currentText);
  692. }
  693. /*
  694. * I need to "pop" the element off the String (currentLoc) which
  695. * always represents my current location in the XML document.
  696. */
  697. int nameLength = name.length() + 1; // Add one for the "\"
  698. int locLength = currentLoc.length();
  699. currentLoc = currentLoc.substring(0, locLength - nameLength);
  700. }
  701. /**
  702. * Receive notification that character data has been found in a standard
  703. * EJB 1.1 descriptor. We're interested in retrieving the home
  704. * interface, remote interface, implementation class, the type of bean,
  705. * and if the bean uses CMP.
  706. *
  707. * @param value String data found in the XML document.
  708. */
  709. private void stdCharacters(String value) {
  710. if (currentLoc.equals("\\ejb-jar\\display-name")) {
  711. displayName = value;
  712. return;
  713. }
  714. String base = "\\ejb-jar\\enterprise-beans\\" + ejbType;
  715. if (currentLoc.equals(base + "\\ejb-name")) {
  716. currentEjb = (EjbInfo) ejbs.get(value);
  717. if (currentEjb == null) {
  718. currentEjb = new EjbInfo(value);
  719. ejbs.put(value, currentEjb);
  720. }
  721. } else if (currentLoc.equals(base + "\\home")) {
  722. currentEjb.setHome(value);
  723. } else if (currentLoc.equals(base + "\\remote")) {
  724. currentEjb.setRemote(value);
  725. } else if (currentLoc.equals(base + "\\ejb-class")) {
  726. currentEjb.setImplementation(value);
  727. } else if (currentLoc.equals(base + "\\prim-key-class")) {
  728. currentEjb.setPrimaryKey(value);
  729. } else if (currentLoc.equals(base + "\\session-type")) {
  730. currentEjb.setBeantype(value);
  731. } else if (currentLoc.equals(base + "\\persistence-type")) {
  732. currentEjb.setCmp(value);
  733. }
  734. }
  735. /**
  736. * Receive notification that character data has been found in an
  737. * iAS-specific descriptor. We're interested in retrieving data
  738. * indicating whether the bean must support RMI/IIOP access, whether
  739. * the bean must provide highly available stubs and skeletons (in the
  740. * case of stateful session beans), and if this bean uses additional
  741. * CMP XML descriptors (in the case of entity beans with CMP).
  742. *
  743. * @param value String data found in the XML document.
  744. */
  745. private void iasCharacters(String value) {
  746. String base = "\\ias-ejb-jar\\enterprise-beans\\" + ejbType;
  747. if (currentLoc.equals(base + "\\ejb-name")) {
  748. currentEjb = (EjbInfo) ejbs.get(value);
  749. if (currentEjb == null) {
  750. currentEjb = new EjbInfo(value);
  751. ejbs.put(value, currentEjb);
  752. }
  753. } else if (currentLoc.equals(base + "\\iiop")) {
  754. currentEjb.setIiop(value);
  755. } else if (currentLoc.equals(base + "\\failover-required")) {
  756. currentEjb.setHasession(value);
  757. } else if (currentLoc.equals(base + "\\persistence-manager"
  758. + "\\properties-file-location")) {
  759. currentEjb.addCmpDescriptor(value);
  760. }
  761. }
  762. } // End of EjbcHandler inner class
  763. /**
  764. * This inner class represents an EJB that will be compiled using ejbc.
  765. *
  766. */
  767. private class EjbInfo {
  768. private String name; // EJB's display name
  769. private Classname home; // EJB's home interface name
  770. private Classname remote; // EJB's remote interface name
  771. private Classname implementation; // EJB's implementation class
  772. private Classname primaryKey; // EJB's primary key class
  773. private String beantype = "entity"; // or "stateful" or "stateless"
  774. private boolean cmp = false; // Does this EJB support CMP?
  775. private boolean iiop = false; // Does this EJB support IIOP?
  776. private boolean hasession = false; // Does this EJB require failover?
  777. private List cmpDescriptors = new ArrayList(); // CMP descriptor list
  778. /**
  779. * Construct a new EJBInfo object with the given name.
  780. *
  781. * @param name The display name for the EJB.
  782. */
  783. public EjbInfo(String name) {
  784. this.name = name;
  785. }
  786. /**
  787. * Returns the display name of the EJB. If a display name has not been
  788. * set, it returns the EJB implementation classname (if the
  789. * implementation class is not set, it returns "[unnamed]").
  790. *
  791. * @return The display name for the EJB.
  792. */
  793. public String getName() {
  794. if (name == null) {
  795. if (implementation == null) {
  796. return "[unnamed]";
  797. } else {
  798. return implementation.getClassName();
  799. }
  800. }
  801. return name;
  802. }
  803. /*
  804. * Below are getter's and setter's for each of the instance variables.
  805. * Note that (in addition to supporting setters with the same type as
  806. * the instance variable) a setter is provided with takes a String
  807. * argument -- this are provided so the XML document handler can set
  808. * the EJB values using the Strings it parses.
  809. */
  810. public void setHome(String home) {
  811. setHome(new Classname(home));
  812. }
  813. public void setHome(Classname home) {
  814. this.home = home;
  815. }
  816. public Classname getHome() {
  817. return home;
  818. }
  819. public void setRemote(String remote) {
  820. setRemote(new Classname(remote));
  821. }
  822. public void setRemote(Classname remote) {
  823. this.remote = remote;
  824. }
  825. public Classname getRemote() {
  826. return remote;
  827. }
  828. public void setImplementation(String implementation) {
  829. setImplementation(new Classname(implementation));
  830. }
  831. public void setImplementation(Classname implementation) {
  832. this.implementation = implementation;
  833. }
  834. public Classname getImplementation() {
  835. return implementation;
  836. }
  837. public void setPrimaryKey(String primaryKey) {
  838. setPrimaryKey(new Classname(primaryKey));
  839. }
  840. public void setPrimaryKey(Classname primaryKey) {
  841. this.primaryKey = primaryKey;
  842. }
  843. public Classname getPrimaryKey() {
  844. return primaryKey;
  845. }
  846. public void setBeantype(String beantype) {
  847. this.beantype = beantype.toLowerCase();
  848. }
  849. public String getBeantype() {
  850. return beantype;
  851. }
  852. public void setCmp(boolean cmp) {
  853. this.cmp = cmp;
  854. }
  855. public void setCmp(String cmp) {
  856. setCmp(cmp.equals("Container"));
  857. }
  858. public boolean getCmp() {
  859. return cmp;
  860. }
  861. public void setIiop(boolean iiop) {
  862. this.iiop = iiop;
  863. }
  864. public void setIiop(String iiop) {
  865. setIiop(iiop.equals("true"));
  866. }
  867. public boolean getIiop() {
  868. return iiop;
  869. }
  870. public void setHasession(boolean hasession) {
  871. this.hasession = hasession;
  872. }
  873. public void setHasession(String hasession) {
  874. setHasession(hasession.equals("true"));
  875. }
  876. public boolean getHasession() {
  877. return hasession;
  878. }
  879. public void addCmpDescriptor(String descriptor) {
  880. cmpDescriptors.add(descriptor);
  881. }
  882. public List getCmpDescriptors() {
  883. return cmpDescriptors;
  884. }
  885. /**
  886. * Verifies that the EJB is valid--if it is invalid, an exception is
  887. * thrown
  888. *
  889. *
  890. * @param buildDir The directory where the EJB remote interface, home
  891. * interface, and implementation class must be found.
  892. * @throws EjbcException If the EJB is invalid.
  893. */
  894. private void checkConfiguration(File buildDir) throws EjbcException {
  895. /* Check that the specified instance variables are valid */
  896. if (home == null) {
  897. throw new EjbcException("A home interface was not found "
  898. + "for the " + name + " EJB.");
  899. }
  900. if (remote == null) {
  901. throw new EjbcException("A remote interface was not found "
  902. + "for the " + name + " EJB.");
  903. }
  904. if (implementation == null) {
  905. throw new EjbcException("An EJB implementation class was not "
  906. + "found for the " + name + " EJB.");
  907. }
  908. if ((!beantype.equals(ENTITY_BEAN))
  909. && (!beantype.equals(STATELESS_SESSION))
  910. && (!beantype.equals(STATEFUL_SESSION))) {
  911. throw new EjbcException("The beantype found (" + beantype + ") "
  912. + "isn't valid in the " + name + " EJB.");
  913. }
  914. if (cmp && (!beantype.equals(ENTITY_BEAN))) {
  915. System.out.println("CMP stubs and skeletons may not be generated"
  916. + " for a Session Bean -- the \"cmp\" attribute will be"
  917. + " ignoredfor the " + name + " EJB.");
  918. }
  919. if (hasession && (!beantype.equals(STATEFUL_SESSION))) {
  920. System.out.println("Highly available stubs and skeletons may "
  921. + "only be generated for a Stateful Session Bean -- the "
  922. + "\"hasession\" attribute will be ignored for the "
  923. + name + " EJB.");
  924. }
  925. /* Check that the EJB "source" classes all exist */
  926. if (!remote.getClassFile(buildDir).exists()) {
  927. throw new EjbcException("The remote interface "
  928. + remote.getQualifiedClassName() + " could not be "
  929. + "found.");
  930. }
  931. if (!home.getClassFile(buildDir).exists()) {
  932. throw new EjbcException("The home interface "
  933. + home.getQualifiedClassName() + " could not be "
  934. + "found.");
  935. }
  936. if (!implementation.getClassFile(buildDir).exists()) {
  937. throw new EjbcException("The EJB implementation class "
  938. + implementation.getQualifiedClassName() + " could "
  939. + "not be found.");
  940. }
  941. }
  942. /**
  943. * Determines if the ejbc utility needs to be run or not. If the stubs
  944. * and skeletons can all be found in the destination directory AND all
  945. * of their timestamps are more recent than the EJB source classes
  946. * (home, remote, and implementation classes), the method returns
  947. * <code>false</code>. Otherwise, the method returns <code>true</code>.
  948. *
  949. * @param destDir The directory where the EJB source classes, stubs and
  950. * skeletons are located.
  951. * @return A boolean indicating whether or not the ejbc utility needs to
  952. * be run to bring the stubs and skeletons up to date.
  953. */
  954. public boolean mustBeRecompiled(File destDir) {
  955. long sourceModified = sourceClassesModified(destDir);
  956. long destModified = destClassesModified(destDir);
  957. return (destModified < sourceModified);
  958. }
  959. /**
  960. * Examines each of the EJB source classes (home, remote, and
  961. * implementation) and returns the modification timestamp for the
  962. * "oldest" class.
  963. *
  964. * @param classpath The classpath to be used to find the source EJB
  965. * classes. If <code>null</code>, the system classpath
  966. * is used.
  967. * @return The modification timestamp for the "oldest" EJB source class.
  968. * @throws BuildException If one of the EJB source classes cannot be
  969. * found on the classpath.
  970. */
  971. private long sourceClassesModified(File buildDir) {
  972. long latestModified; // The timestamp of the "newest" class
  973. long modified; // Timestamp for a given class
  974. File remoteFile; // File for the remote interface class
  975. File homeFile; // File for the home interface class
  976. File implFile; // File for the EJB implementation class
  977. File pkFile; // File for the EJB primary key class
  978. /* Check the timestamp on the remote interface */
  979. remoteFile = remote.getClassFile(buildDir);
  980. modified = remoteFile.lastModified();
  981. if (modified == -1) {
  982. System.out.println("The class "
  983. + remote.getQualifiedClassName() + " couldn't "
  984. + "be found on the classpath");
  985. return -1;
  986. }
  987. latestModified = modified;
  988. /* Check the timestamp on the home interface */
  989. homeFile = home.getClassFile(buildDir);
  990. modified = homeFile.lastModified();
  991. if (modified == -1) {
  992. System.out.println("The class "
  993. + home.getQualifiedClassName() + " couldn't be "
  994. + "found on the classpath");
  995. return -1;
  996. }
  997. latestModified = Math.max(latestModified, modified);
  998. /* Check the timestamp of the primary key class */
  999. if (primaryKey != null) {
  1000. pkFile = primaryKey.getClassFile(buildDir);
  1001. modified = pkFile.lastModified();
  1002. if (modified == -1) {
  1003. System.out.println("The class "
  1004. + primaryKey.getQualifiedClassName() + "couldn't be "
  1005. + "found on the classpath");
  1006. return -1;
  1007. }
  1008. latestModified = Math.max(latestModified, modified);
  1009. } else {
  1010. pkFile = null;
  1011. }
  1012. /* Check the timestamp on the EJB implementation class.
  1013. *
  1014. * Note that if ONLY the implementation class has changed, it's not
  1015. * necessary to rebuild the EJB stubs and skeletons. For this
  1016. * reason, we ensure the file exists (using lastModified above), but
  1017. * we DON'T compare it's timestamp with the timestamps of the home
  1018. * and remote interfaces (because it's irrelevant in determining if
  1019. * ejbc must be run)
  1020. */
  1021. implFile = implementation.getClassFile(buildDir);
  1022. modified = implFile.lastModified();
  1023. if (modified == -1) {
  1024. System.out.println("The class "
  1025. + implementation.getQualifiedClassName()
  1026. + " couldn't be found on the classpath");
  1027. return -1;
  1028. }
  1029. String pathToFile = remote.getQualifiedClassName();
  1030. pathToFile = pathToFile.replace('.', File.separatorChar) + ".class";
  1031. ejbFiles.put(pathToFile, remoteFile);
  1032. pathToFile = home.getQualifiedClassName();
  1033. pathToFile = pathToFile.replace('.', File.separatorChar) + ".class";
  1034. ejbFiles.put(pathToFile, homeFile);
  1035. pathToFile = implementation.getQualifiedClassName();
  1036. pathToFile = pathToFile.replace('.', File.separatorChar) + ".class";
  1037. ejbFiles.put(pathToFile, implFile);
  1038. if (pkFile != null) {
  1039. pathToFile = primaryKey.getQualifiedClassName();
  1040. pathToFile = pathToFile.replace('.', File.separatorChar) + ".class";
  1041. ejbFiles.put(pathToFile, pkFile);
  1042. }
  1043. return latestModified;
  1044. }
  1045. /**
  1046. * Examines each of the EJB stubs and skeletons in the destination
  1047. * directory and returns the modification timestamp for the "oldest"
  1048. * class. If one of the stubs or skeletons cannot be found, <code>-1
  1049. * </code> is returned.
  1050. *
  1051. * @param dest The directory in which the EJB stubs and skeletons are
  1052. * stored.
  1053. * @return The modification timestamp for the "oldest" EJB stub or
  1054. * skeleton. If one of the classes cannot be found, <code>-1
  1055. * </code> is returned.
  1056. * @throws BuildException If the canonical path of the destination
  1057. * directory cannot be found.
  1058. */
  1059. private long destClassesModified(File destDir) {
  1060. String[] classnames = classesToGenerate(); // List of all stubs & skels
  1061. long destClassesModified = new Date().getTime(); // Earliest mod time
  1062. boolean allClassesFound = true; // Has each been found?
  1063. /*
  1064. * Loop through each stub/skeleton class that must be generated, and
  1065. * determine (if all exist) which file has the most recent timestamp
  1066. */
  1067. for (int i = 0; i < classnames.length; i++) {
  1068. String pathToClass =
  1069. classnames[i].replace('.', File.separatorChar) + ".class";
  1070. File classFile = new File(destDir, pathToClass);
  1071. /*
  1072. * Add each stub/skeleton class to the list of EJB files. Note
  1073. * that each class is added even if it doesn't exist now.
  1074. */
  1075. ejbFiles.put(pathToClass, classFile);
  1076. allClassesFound = allClassesFound && classFile.exists();
  1077. if (allClassesFound) {
  1078. long fileMod = classFile.lastModified();
  1079. /* Keep track of the oldest modification timestamp */
  1080. destClassesModified = Math.min(destClassesModified, fileMod);
  1081. }
  1082. }
  1083. return (allClassesFound) ? destClassesModified : -1;
  1084. }
  1085. /**
  1086. * Builds an array of class names which represent the stubs and
  1087. * skeletons which need to be generated for a given EJB. The class
  1088. * names are fully qualified. Nine classes are generated for all EJBs
  1089. * while an additional six classes are generated for beans requiring
  1090. * RMI/IIOP access.
  1091. *
  1092. * @return An array of Strings representing the fully-qualified class
  1093. * names for the stubs and skeletons to be generated.
  1094. */
  1095. private String[] classesToGenerate() {
  1096. String[] classnames = (iiop) ? new String[15] : new String[9];
  1097. final String remotePkg = remote.getPackageName() + ".";
  1098. final String remoteClass = remote.getClassName();
  1099. final String homePkg = home.getPackageName() + ".";
  1100. final String homeClass = home.getClassName();
  1101. final String implPkg = implementation.getPackageName() + ".";
  1102. final String implFullClass = implementation.getQualifiedWithUnderscores();
  1103. int index = 0;
  1104. classnames[index++] = implPkg + "ejb_fac_" + implFullClass;
  1105. classnames[index++] = implPkg + "ejb_home_" + implFullClass;
  1106. classnames[index++] = implPkg + "ejb_skel_" + implFullClass;
  1107. classnames[index++] = remotePkg + "ejb_kcp_skel_" + remoteClass;
  1108. classnames[index++] = homePkg + "ejb_kcp_skel_" + homeClass;
  1109. classnames[index++] = remotePkg + "ejb_kcp_stub_" + remoteClass;
  1110. classnames[index++] = homePkg + "ejb_kcp_stub_" + homeClass;
  1111. classnames[index++] = remotePkg + "ejb_stub_" + remoteClass;
  1112. classnames[index++] = homePkg + "ejb_stub_" + homeClass;
  1113. if (!iiop) {
  1114. return classnames;
  1115. }
  1116. classnames[index++] = "org.omg.stub." + remotePkg + "_"
  1117. + remoteClass + "_Stub";
  1118. classnames[index++] = "org.omg.stub." + homePkg + "_"
  1119. + homeClass + "_Stub";
  1120. classnames[index++] = "org.omg.stub." + remotePkg
  1121. + "_ejb_RmiCorbaBridge_"
  1122. + remoteClass + "_Tie";
  1123. classnames[index++] = "org.omg.stub." + homePkg
  1124. + "_ejb_RmiCorbaBridge_"
  1125. + homeClass + "_Tie";
  1126. classnames[index++] = remotePkg + "ejb_RmiCorbaBridge_"
  1127. + remoteClass;
  1128. classnames[index++] = homePkg + "ejb_RmiCorbaBridge_" + homeClass;
  1129. return classnames;
  1130. }
  1131. /**
  1132. * Convenience method which creates a String representation of all the
  1133. * instance variables of an EjbInfo object.
  1134. *
  1135. * @return A String representing the EjbInfo instance.
  1136. */
  1137. public String toString() {
  1138. String s = "EJB name: " + name
  1139. + "\n\r home: " + home
  1140. + "\n\r remote: " + remote
  1141. + "\n\r impl: " + implementation
  1142. + "\n\r primaryKey: " + primaryKey
  1143. + "\n\r beantype: " + beantype
  1144. + "\n\r cmp: " + cmp
  1145. + "\n\r iiop: " + iiop
  1146. + "\n\r hasession: " + hasession;
  1147. Iterator i = cmpDescriptors.iterator();
  1148. while (i.hasNext()) {
  1149. s += "\n\r CMP Descriptor: " + i.next();
  1150. }
  1151. return s;
  1152. }
  1153. } // End of EjbInfo inner class
  1154. /**
  1155. * Convenience class used to represent the fully qualified name of a Java
  1156. * class. It provides an easy way to retrieve components of the class name
  1157. * in a format that is convenient for building iAS stubs and skeletons.
  1158. *
  1159. */
  1160. private static class Classname {
  1161. private String qualifiedName; // Fully qualified name of the Java class
  1162. private String packageName; // Name of the package for this class
  1163. private String className; // Name of the class without the package
  1164. /**
  1165. * This constructor builds an object which represents the name of a Java
  1166. * class.
  1167. *
  1168. * @param qualifiedName String representing the fully qualified class
  1169. * name of the Java class.
  1170. */
  1171. public Classname(String qualifiedName) {
  1172. if (qualifiedName == null) {
  1173. return;
  1174. }
  1175. this.qualifiedName = qualifiedName;
  1176. int index = qualifiedName.lastIndexOf('.');
  1177. if (index == -1) {
  1178. className = qualifiedName;
  1179. packageName = "";
  1180. } else {
  1181. packageName = qualifiedName.substring(0, index);
  1182. className = qualifiedName.substring(index + 1);
  1183. }
  1184. }
  1185. /**
  1186. * Gets the fully qualified name of the Java class.
  1187. *
  1188. * @return String representing the fully qualified class name.
  1189. */
  1190. public String getQualifiedClassName() {
  1191. return qualifiedName;
  1192. }
  1193. /**
  1194. * Gets the package name for the Java class.
  1195. *
  1196. * @return String representing the package name for the class.
  1197. */
  1198. public String getPackageName() {
  1199. return packageName;
  1200. }
  1201. /**
  1202. * Gets the Java class name without the package structure.
  1203. *
  1204. * @return String representing the name for the class.
  1205. */
  1206. public String getClassName() {
  1207. return className;
  1208. }
  1209. /**
  1210. * Gets the fully qualified name of the Java class with underscores
  1211. * separating the components of the class name rather than periods.
  1212. * This format is used in naming some of the stub and skeleton classes
  1213. * for the iPlanet Application Server.
  1214. *
  1215. * @return String representing the fully qualified class name using
  1216. * underscores instead of periods.
  1217. */
  1218. public String getQualifiedWithUnderscores() {
  1219. return qualifiedName.replace('.', '_');
  1220. }
  1221. /**
  1222. * Returns a File which references the class relative to the specified
  1223. * directory. Note that the class file may or may not exist.
  1224. *
  1225. * @param directory A File referencing the base directory containing
  1226. * class files.
  1227. * @return File referencing this class.
  1228. */
  1229. public File getClassFile(File directory) {
  1230. String pathToFile = qualifiedName.replace('.', File.separatorChar)
  1231. + ".class";
  1232. return new File(directory, pathToFile);
  1233. }
  1234. /**
  1235. * String representation of this class name. It returns the fully
  1236. * qualified class name.
  1237. *
  1238. * @return String representing the fully qualified class name.
  1239. */
  1240. public String toString() {
  1241. return getQualifiedClassName();
  1242. }
  1243. } // End of Classname inner class
  1244. /**
  1245. * Thread class used to redirect output from an <code>InputStream</code> to
  1246. * the JRE standard output. This class may be used to redirect output from
  1247. * an external process to the standard output.
  1248. *
  1249. */
  1250. private static class RedirectOutput extends Thread {
  1251. InputStream stream; // Stream to read and redirect to standard output
  1252. /**
  1253. * Constructs a new instance that will redirect output from the
  1254. * specified stream to the standard output.
  1255. *
  1256. * @param stream InputStream which will be read and redirected to the
  1257. * standard output.
  1258. */
  1259. public RedirectOutput(InputStream stream) {
  1260. this.stream = stream;
  1261. }
  1262. /**
  1263. * Reads text from the input stream and redirects it to standard output
  1264. * using a separate thread.
  1265. */
  1266. public void run() {
  1267. BufferedReader reader = new BufferedReader(
  1268. new InputStreamReader(stream));
  1269. String text;
  1270. try {
  1271. while ((text = reader.readLine()) != null) {
  1272. System.out.println(text);
  1273. }
  1274. } catch (IOException e) {
  1275. e.printStackTrace();
  1276. } finally {
  1277. try {
  1278. reader.close();
  1279. } catch (IOException e) {
  1280. // Do nothing
  1281. }
  1282. }
  1283. }
  1284. } // End of RedirectOutput inner class
  1285. }