1. /*
  2. * Copyright 2000-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.File;
  19. import java.io.FileInputStream;
  20. import java.io.FileOutputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.util.ArrayList;
  24. import java.util.Enumeration;
  25. import java.util.Hashtable;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import java.util.jar.JarOutputStream;
  29. import java.util.jar.Manifest;
  30. import java.util.zip.ZipEntry;
  31. import javax.xml.parsers.SAXParser;
  32. import org.apache.tools.ant.BuildException;
  33. import org.apache.tools.ant.DirectoryScanner;
  34. import org.apache.tools.ant.Location;
  35. import org.apache.tools.ant.Project;
  36. import org.apache.tools.ant.Task;
  37. import org.apache.tools.ant.types.FileSet;
  38. import org.apache.tools.ant.types.Path;
  39. import org.apache.tools.ant.util.depend.DependencyAnalyzer;
  40. import org.xml.sax.InputSource;
  41. import org.xml.sax.SAXException;
  42. /**
  43. * A deployment tool which creates generic EJB jars. Generic jars contains
  44. * only those classes and META-INF entries specified in the EJB 1.1 standard
  45. *
  46. * This class is also used as a framework for the creation of vendor specific
  47. * deployment tools. A number of template methods are provided through which the
  48. * vendor specific tool can hook into the EJB creation process.
  49. *
  50. */
  51. public class GenericDeploymentTool implements EJBDeploymentTool {
  52. /** The standard META-INF directory in jar files */
  53. protected static final String META_DIR = "META-INF/";
  54. /** The standard MANIFEST file */
  55. protected static final String MANIFEST = META_DIR + "MANIFEST.MF";
  56. /** Name for EJB Deployment descriptor within EJB jars */
  57. protected static final String EJB_DD = "ejb-jar.xml";
  58. /** A dependency analyzer name to find ancestor classes */
  59. public static final String ANALYZER_SUPER = "super";
  60. /** A dependency analyzer name to find all related classes */
  61. public static final String ANALYZER_FULL = "full";
  62. /** A dependency analyzer name for no analyzer */
  63. public static final String ANALYZER_NONE = "none";
  64. /** The default analyzer */
  65. public static final String DEFAULT_ANALYZER = ANALYZER_SUPER;
  66. /** The analyzer class for the super analyzer */
  67. public static final String ANALYZER_CLASS_SUPER
  68. = "org.apache.tools.ant.util.depend.bcel.AncestorAnalyzer";
  69. /** The analyzer class for the super analyzer */
  70. public static final String ANALYZER_CLASS_FULL
  71. = "org.apache.tools.ant.util.depend.bcel.FullAnalyzer";
  72. /**
  73. * The configuration from the containing task. This config combined
  74. * with the settings of the individual attributes here constitues the
  75. * complete config for this deployment tool.
  76. */
  77. private EjbJar.Config config;
  78. /** Stores a handle to the directory to put the Jar files in */
  79. private File destDir;
  80. /** The classpath to use with this deployment tool. This is appended to
  81. any paths from the ejbjar task itself.*/
  82. private Path classpath;
  83. /** Instance variable that stores the suffix for the generated jarfile. */
  84. private String genericJarSuffix = "-generic.jar";
  85. /**
  86. * The task to which this tool belongs. This is used to access services
  87. * provided by the ant core, such as logging.
  88. */
  89. private Task task;
  90. /**
  91. * The classloader generated from the given classpath to load
  92. * the super classes and super interfaces.
  93. */
  94. private ClassLoader classpathLoader = null;
  95. /**
  96. * List of files have been loaded into the EJB jar
  97. */
  98. private List addedfiles;
  99. /**
  100. * Handler used to parse the EJB XML descriptor
  101. */
  102. private DescriptorHandler handler;
  103. /**
  104. * Dependency analyzer used to collect class dependencies
  105. */
  106. private DependencyAnalyzer dependencyAnalyzer;
  107. public GenericDeploymentTool() {
  108. }
  109. /**
  110. * Set the destination directory; required.
  111. * @param inDir the destination directory.
  112. */
  113. public void setDestdir(File inDir) {
  114. this.destDir = inDir;
  115. }
  116. /**
  117. * Get the destination directory.
  118. *
  119. * @return the destination directory into which EJB jars are to be written
  120. */
  121. protected File getDestDir() {
  122. return destDir;
  123. }
  124. /**
  125. * Set the task which owns this tool
  126. *
  127. * @param task the Task to which this deployment tool is associated.
  128. */
  129. public void setTask(Task task) {
  130. this.task = task;
  131. }
  132. /**
  133. * Get the task for this tool.
  134. *
  135. * @return the Task instance this tool is associated with.
  136. */
  137. protected Task getTask() {
  138. return task;
  139. }
  140. /**
  141. * Get the basename terminator.
  142. *
  143. * @return an ejbjar task configuration
  144. */
  145. protected EjbJar.Config getConfig() {
  146. return config;
  147. }
  148. /**
  149. * Indicate if this build is using the base jar name.
  150. *
  151. * @return true if the name of the generated jar is coming from the
  152. * basejarname attribute
  153. */
  154. protected boolean usingBaseJarName() {
  155. return config.baseJarName != null;
  156. }
  157. /**
  158. * Set the suffix for the generated jar file.
  159. * @param inString the string to use as the suffix.
  160. */
  161. public void setGenericJarSuffix(String inString) {
  162. this.genericJarSuffix = inString;
  163. }
  164. /**
  165. * Add the classpath for the user classes
  166. *
  167. * @return a Path instance to be configured by Ant.
  168. */
  169. public Path createClasspath() {
  170. if (classpath == null) {
  171. classpath = new Path(task.getProject());
  172. }
  173. return classpath.createPath();
  174. }
  175. /**
  176. * Set the classpath to be used for this compilation.
  177. *
  178. * @param classpath the classpath to be used for this build.
  179. */
  180. public void setClasspath(Path classpath) {
  181. this.classpath = classpath;
  182. }
  183. /**
  184. * Get the classpath by combining the one from the surrounding task, if any
  185. * and the one from this tool.
  186. *
  187. * @return the combined classpath
  188. */
  189. protected Path getCombinedClasspath() {
  190. Path combinedPath = classpath;
  191. if (config.classpath != null) {
  192. if (combinedPath == null) {
  193. combinedPath = config.classpath;
  194. } else {
  195. combinedPath.append(config.classpath);
  196. }
  197. }
  198. return combinedPath;
  199. }
  200. /**
  201. * Log a message to the Ant output.
  202. *
  203. * @param message the message to be logged.
  204. * @param level the severity of this message.
  205. */
  206. protected void log(String message, int level) {
  207. getTask().log(message, level);
  208. }
  209. /**
  210. * Get the build file location associated with this element's task.
  211. *
  212. * @return the task's location instance.
  213. */
  214. protected Location getLocation() {
  215. return getTask().getLocation();
  216. }
  217. private void createAnalyzer() {
  218. String analyzer = config.analyzer;
  219. if (analyzer == null) {
  220. analyzer = DEFAULT_ANALYZER;
  221. }
  222. if (analyzer.equals(ANALYZER_NONE)) {
  223. return;
  224. }
  225. String analyzerClassName = null;
  226. if (analyzer.equals(ANALYZER_SUPER)) {
  227. analyzerClassName = ANALYZER_CLASS_SUPER;
  228. } else if (analyzer.equals(ANALYZER_FULL)) {
  229. analyzerClassName = ANALYZER_CLASS_FULL;
  230. } else {
  231. analyzerClassName = analyzer;
  232. }
  233. try {
  234. Class analyzerClass = Class.forName(analyzerClassName);
  235. dependencyAnalyzer
  236. = (DependencyAnalyzer) analyzerClass.newInstance();
  237. dependencyAnalyzer.addClassPath(new Path(task.getProject(),
  238. config.srcDir.getPath()));
  239. dependencyAnalyzer.addClassPath(config.classpath);
  240. } catch (NoClassDefFoundError e) {
  241. dependencyAnalyzer = null;
  242. task.log("Unable to load dependency analyzer: " + analyzerClassName
  243. + " - dependent class not found: " + e.getMessage(),
  244. Project.MSG_WARN);
  245. } catch (Exception e) {
  246. dependencyAnalyzer = null;
  247. task.log("Unable to load dependency analyzer: " + analyzerClassName
  248. + " - exception: " + e.getMessage(),
  249. Project.MSG_WARN);
  250. }
  251. }
  252. /**
  253. * Configure this tool for use in the ejbjar task.
  254. *
  255. * @param config the configuration from the surrounding ejbjar task.
  256. */
  257. public void configure(EjbJar.Config config) {
  258. this.config = config;
  259. createAnalyzer();
  260. classpathLoader = null;
  261. }
  262. /**
  263. * Utility method that encapsulates the logic of adding a file entry to
  264. * a .jar file. Used by execute() to add entries to the jar file as it is
  265. * constructed.
  266. * @param jStream A JarOutputStream into which to write the
  267. * jar entry.
  268. * @param inputFile A File from which to read the
  269. * contents the file being added.
  270. * @param logicalFilename A String representing the name, including
  271. * all relevant path information, that should be stored for the entry
  272. * being added.
  273. */
  274. protected void addFileToJar(JarOutputStream jStream,
  275. File inputFile,
  276. String logicalFilename)
  277. throws BuildException {
  278. FileInputStream iStream = null;
  279. try {
  280. if (!addedfiles.contains(logicalFilename)) {
  281. iStream = new FileInputStream(inputFile);
  282. // Create the zip entry and add it to the jar file
  283. ZipEntry zipEntry = new ZipEntry(logicalFilename.replace('\\', '/'));
  284. jStream.putNextEntry(zipEntry);
  285. // Create the file input stream, and buffer everything over
  286. // to the jar output stream
  287. byte[] byteBuffer = new byte[2 * 1024];
  288. int count = 0;
  289. do {
  290. jStream.write(byteBuffer, 0, count);
  291. count = iStream.read(byteBuffer, 0, byteBuffer.length);
  292. } while (count != -1);
  293. //add it to list of files in jar
  294. addedfiles.add(logicalFilename);
  295. }
  296. } catch (IOException ioe) {
  297. log("WARNING: IOException while adding entry "
  298. + logicalFilename + " to jarfile from "
  299. + inputFile.getPath() + " " + ioe.getClass().getName()
  300. + "-" + ioe.getMessage(), Project.MSG_WARN);
  301. } finally {
  302. // Close up the file input stream for the class file
  303. if (iStream != null) {
  304. try {
  305. iStream.close();
  306. } catch (IOException closeException) {
  307. // ignore
  308. }
  309. }
  310. }
  311. }
  312. protected DescriptorHandler getDescriptorHandler(File srcDir) {
  313. DescriptorHandler handler = new DescriptorHandler(getTask(), srcDir);
  314. registerKnownDTDs(handler);
  315. // register any DTDs supplied by the user
  316. for (Iterator i = getConfig().dtdLocations.iterator(); i.hasNext();) {
  317. EjbJar.DTDLocation dtdLocation = (EjbJar.DTDLocation) i.next();
  318. handler.registerDTD(dtdLocation.getPublicId(), dtdLocation.getLocation());
  319. }
  320. return handler;
  321. }
  322. /**
  323. * Register the locations of all known DTDs.
  324. *
  325. * vendor-specific subclasses should override this method to define
  326. * the vendor-specific locations of the EJB DTDs
  327. */
  328. protected void registerKnownDTDs(DescriptorHandler handler) {
  329. // none to register for generic
  330. }
  331. public void processDescriptor(String descriptorFileName, SAXParser saxParser) {
  332. checkConfiguration(descriptorFileName, saxParser);
  333. try {
  334. handler = getDescriptorHandler(config.srcDir);
  335. // Retrive the files to be added to JAR from EJB descriptor
  336. Hashtable ejbFiles = parseEjbFiles(descriptorFileName, saxParser);
  337. // Add any support classes specified in the build file
  338. addSupportClasses(ejbFiles);
  339. // Determine the JAR filename (without filename extension)
  340. String baseName = getJarBaseName(descriptorFileName);
  341. String ddPrefix = getVendorDDPrefix(baseName, descriptorFileName);
  342. File manifestFile = getManifestFile(ddPrefix);
  343. if (manifestFile != null) {
  344. ejbFiles.put(MANIFEST, manifestFile);
  345. }
  346. // First the regular deployment descriptor
  347. ejbFiles.put(META_DIR + EJB_DD,
  348. new File(config.descriptorDir, descriptorFileName));
  349. // now the vendor specific files, if any
  350. addVendorFiles(ejbFiles, ddPrefix);
  351. // add any dependent files
  352. checkAndAddDependants(ejbFiles);
  353. // Lastly create File object for the Jar files. If we are using
  354. // a flat destination dir, then we need to redefine baseName!
  355. if (config.flatDestDir && baseName.length() != 0) {
  356. int startName = baseName.lastIndexOf(File.separator);
  357. if (startName == -1) {
  358. startName = 0;
  359. }
  360. int endName = baseName.length();
  361. baseName = baseName.substring(startName, endName);
  362. }
  363. File jarFile = getVendorOutputJarFile(baseName);
  364. // Check to see if we need a build and start doing the work!
  365. if (needToRebuild(ejbFiles, jarFile)) {
  366. // Log that we are going to build...
  367. log("building "
  368. + jarFile.getName()
  369. + " with "
  370. + String.valueOf(ejbFiles.size())
  371. + " files",
  372. Project.MSG_INFO);
  373. // Use helper method to write the jarfile
  374. String publicId = getPublicId();
  375. writeJar(baseName, jarFile, ejbFiles, publicId);
  376. } else {
  377. // Log that the file is up to date...
  378. log(jarFile.toString() + " is up to date.",
  379. Project.MSG_VERBOSE);
  380. }
  381. } catch (SAXException se) {
  382. String msg = "SAXException while parsing '"
  383. + descriptorFileName.toString()
  384. + "'. This probably indicates badly-formed XML."
  385. + " Details: "
  386. + se.getMessage();
  387. throw new BuildException(msg, se);
  388. } catch (IOException ioe) {
  389. String msg = "IOException while parsing'"
  390. + descriptorFileName.toString()
  391. + "'. This probably indicates that the descriptor"
  392. + " doesn't exist. Details: "
  393. + ioe.getMessage();
  394. throw new BuildException(msg, ioe);
  395. }
  396. }
  397. /**
  398. * This method is called as the first step in the processDescriptor method
  399. * to allow vendor-specific subclasses to validate the task configuration
  400. * prior to processing the descriptor. If the configuration is invalid,
  401. * a BuildException should be thrown.
  402. *
  403. * @param descriptorFileName String representing the file name of an EJB
  404. * descriptor to be processed
  405. * @param saxParser SAXParser which may be used to parse the XML
  406. * descriptor
  407. * @exception BuildException Thrown if the configuration is invalid
  408. */
  409. protected void checkConfiguration(String descriptorFileName,
  410. SAXParser saxParser) throws BuildException {
  411. /*
  412. * For the GenericDeploymentTool, do nothing. Vendor specific
  413. * subclasses should throw a BuildException if the configuration is
  414. * invalid for their server.
  415. */
  416. }
  417. /**
  418. * This method returns a list of EJB files found when the specified EJB
  419. * descriptor is parsed and processed.
  420. *
  421. * @param descriptorFileName String representing the file name of an EJB
  422. * descriptor to be processed
  423. * @param saxParser SAXParser which may be used to parse the XML
  424. * descriptor
  425. * @return Hashtable of EJB class (and other) files to be
  426. * added to the completed JAR file
  427. * @throws SAXException Any SAX exception, possibly wrapping another
  428. * exception
  429. * @throws IOException An IOException from the parser, possibly from a
  430. * the byte stream or character stream
  431. */
  432. protected Hashtable parseEjbFiles(String descriptorFileName, SAXParser saxParser)
  433. throws IOException, SAXException {
  434. FileInputStream descriptorStream = null;
  435. Hashtable ejbFiles = null;
  436. try {
  437. /* Parse the ejb deployment descriptor. While it may not
  438. * look like much, we use a SAXParser and an inner class to
  439. * get hold of all the classfile names for the descriptor.
  440. */
  441. descriptorStream
  442. = new FileInputStream(new File(config.descriptorDir, descriptorFileName));
  443. saxParser.parse(new InputSource(descriptorStream), handler);
  444. ejbFiles = handler.getFiles();
  445. } finally {
  446. if (descriptorStream != null) {
  447. try {
  448. descriptorStream.close();
  449. } catch (IOException closeException) {
  450. // ignore
  451. }
  452. }
  453. }
  454. return ejbFiles;
  455. }
  456. /**
  457. * Adds any classes the user specifies using <i>support</i> nested elements
  458. * to the <code>ejbFiles</code> Hashtable.
  459. *
  460. * @param ejbFiles Hashtable of EJB classes (and other) files that will be
  461. * added to the completed JAR file
  462. */
  463. protected void addSupportClasses(Hashtable ejbFiles) {
  464. // add in support classes if any
  465. Project project = task.getProject();
  466. for (Iterator i = config.supportFileSets.iterator(); i.hasNext();) {
  467. FileSet supportFileSet = (FileSet) i.next();
  468. File supportBaseDir = supportFileSet.getDir(project);
  469. DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner(project);
  470. supportScanner.scan();
  471. String[] supportFiles = supportScanner.getIncludedFiles();
  472. for (int j = 0; j < supportFiles.length; ++j) {
  473. ejbFiles.put(supportFiles[j], new File(supportBaseDir, supportFiles[j]));
  474. }
  475. }
  476. }
  477. /**
  478. * Using the EJB descriptor file name passed from the <code>ejbjar</code>
  479. * task, this method returns the "basename" which will be used to name the
  480. * completed JAR file.
  481. *
  482. * @param descriptorFileName String representing the file name of an EJB
  483. * descriptor to be processed
  484. * @return The "basename" which will be used to name the
  485. * completed JAR file
  486. */
  487. protected String getJarBaseName(String descriptorFileName) {
  488. String baseName = "";
  489. // Work out what the base name is
  490. if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.BASEJARNAME)) {
  491. String canonicalDescriptor = descriptorFileName.replace('\\', '/');
  492. int index = canonicalDescriptor.lastIndexOf('/');
  493. if (index != -1) {
  494. baseName = descriptorFileName.substring(0, index + 1);
  495. }
  496. baseName += config.baseJarName;
  497. } else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {
  498. int lastSeparatorIndex = descriptorFileName.lastIndexOf(File.separator);
  499. int endBaseName = -1;
  500. if (lastSeparatorIndex != -1) {
  501. endBaseName = descriptorFileName.indexOf(config.baseNameTerminator,
  502. lastSeparatorIndex);
  503. } else {
  504. endBaseName = descriptorFileName.indexOf(config.baseNameTerminator);
  505. }
  506. if (endBaseName != -1) {
  507. baseName = descriptorFileName.substring(0, endBaseName);
  508. } else {
  509. throw new BuildException("Unable to determine jar name "
  510. + "from descriptor \"" + descriptorFileName + "\"");
  511. }
  512. } else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.DIRECTORY)) {
  513. File descriptorFile = new File(config.descriptorDir, descriptorFileName);
  514. String path = descriptorFile.getAbsolutePath();
  515. int lastSeparatorIndex
  516. = path.lastIndexOf(File.separator);
  517. if (lastSeparatorIndex == -1) {
  518. throw new BuildException("Unable to determine directory name holding descriptor");
  519. }
  520. String dirName = path.substring(0, lastSeparatorIndex);
  521. int dirSeparatorIndex = dirName.lastIndexOf(File.separator);
  522. if (dirSeparatorIndex != -1) {
  523. dirName = dirName.substring(dirSeparatorIndex + 1);
  524. }
  525. baseName = dirName;
  526. } else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.EJB_NAME)) {
  527. baseName = handler.getEjbName();
  528. }
  529. return baseName;
  530. }
  531. /**
  532. * Get the prefix for vendor deployment descriptors.
  533. *
  534. * This will contain the path and the start of the descriptor name,
  535. * depending on the naming scheme
  536. */
  537. public String getVendorDDPrefix(String baseName, String descriptorFileName) {
  538. String ddPrefix = null;
  539. if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {
  540. ddPrefix = baseName + config.baseNameTerminator;
  541. } else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.BASEJARNAME)
  542. || config.namingScheme.getValue().equals(EjbJar.NamingScheme.EJB_NAME)
  543. || config.namingScheme.getValue().equals(EjbJar.NamingScheme.DIRECTORY)) {
  544. String canonicalDescriptor = descriptorFileName.replace('\\', '/');
  545. int index = canonicalDescriptor.lastIndexOf('/');
  546. if (index == -1) {
  547. ddPrefix = "";
  548. } else {
  549. ddPrefix = descriptorFileName.substring(0, index + 1);
  550. }
  551. }
  552. return ddPrefix;
  553. }
  554. /**
  555. * Add any vendor specific files which should be included in the
  556. * EJB Jar.
  557. */
  558. protected void addVendorFiles(Hashtable ejbFiles, String ddPrefix) {
  559. // nothing to add for generic tool.
  560. }
  561. /**
  562. * Get the vendor specific name of the Jar that will be output. The modification date
  563. * of this jar will be checked against the dependent bean classes.
  564. */
  565. File getVendorOutputJarFile(String baseName) {
  566. return new File(destDir, baseName + genericJarSuffix);
  567. }
  568. /**
  569. * This method checks the timestamp on each file listed in the <code>
  570. * ejbFiles</code> and compares them to the timestamp on the <code>jarFile
  571. * </code>. If the <code>jarFile</code>'s timestamp is more recent than
  572. * each EJB file, <code>true</code> is returned. Otherwise, <code>false
  573. * </code> is returned.
  574. * TODO: find a way to check the manifest-file, that is found by naming convention
  575. *
  576. * @param ejbFiles Hashtable of EJB classes (and other) files that will be
  577. * added to the completed JAR file
  578. * @param jarFile JAR file which will contain all of the EJB classes (and
  579. * other) files
  580. * @return boolean indicating whether or not the <code>jarFile</code>
  581. * is up to date
  582. */
  583. protected boolean needToRebuild(Hashtable ejbFiles, File jarFile) {
  584. if (jarFile.exists()) {
  585. long lastBuild = jarFile.lastModified();
  586. Iterator fileIter = ejbFiles.values().iterator();
  587. // Loop through the files seeing if any has been touched
  588. // more recently than the destination jar.
  589. while (fileIter.hasNext()) {
  590. File currentFile = (File) fileIter.next();
  591. if (lastBuild < currentFile.lastModified()) {
  592. log("Build needed because " + currentFile.getPath() + " is out of date",
  593. Project.MSG_VERBOSE);
  594. return true;
  595. }
  596. }
  597. return false;
  598. }
  599. return true;
  600. }
  601. /**
  602. * Returns the Public ID of the DTD specified in the EJB descriptor. Not
  603. * every vendor-specific <code>DeploymentTool</code> will need to reference
  604. * this value or may want to determine this value in a vendor-specific way.
  605. *
  606. * @return Public ID of the DTD specified in the EJB descriptor.
  607. */
  608. protected String getPublicId() {
  609. return handler.getPublicId();
  610. }
  611. /**
  612. * Get the manifets file to use for building the generic jar.
  613. *
  614. * If the file does not exist the global manifest from the config is used
  615. * otherwise the default Ant manifest will be used.
  616. *
  617. * @param prefix the prefix where to llook for the manifest file based on
  618. * the naming convention.
  619. *
  620. * @return the manifest file or null if the manifest file does not exist
  621. */
  622. protected File getManifestFile(String prefix) {
  623. File manifestFile
  624. = new File(getConfig().descriptorDir, prefix + "manifest.mf");
  625. if (manifestFile.exists()) {
  626. return manifestFile;
  627. }
  628. if (config.manifest != null) {
  629. return config.manifest;
  630. }
  631. return null;
  632. }
  633. /**
  634. * Method used to encapsulate the writing of the JAR file. Iterates over the
  635. * filenames/java.io.Files in the Hashtable stored on the instance variable
  636. * ejbFiles.
  637. */
  638. protected void writeJar(String baseName, File jarfile, Hashtable files,
  639. String publicId) throws BuildException {
  640. JarOutputStream jarStream = null;
  641. try {
  642. // clean the addedfiles Vector
  643. addedfiles = new ArrayList();
  644. /* If the jarfile already exists then whack it and recreate it.
  645. * Should probably think of a more elegant way to handle this
  646. * so that in case of errors we don't leave people worse off
  647. * than when we started =)
  648. */
  649. if (jarfile.exists()) {
  650. jarfile.delete();
  651. }
  652. jarfile.getParentFile().mkdirs();
  653. jarfile.createNewFile();
  654. InputStream in = null;
  655. Manifest manifest = null;
  656. try {
  657. File manifestFile = (File) files.get(MANIFEST);
  658. if (manifestFile != null && manifestFile.exists()) {
  659. in = new FileInputStream(manifestFile);
  660. } else {
  661. String defaultManifest = "/org/apache/tools/ant/defaultManifest.mf";
  662. in = this.getClass().getResourceAsStream(defaultManifest);
  663. if (in == null) {
  664. throw new BuildException("Could not find "
  665. + "default manifest: " + defaultManifest);
  666. }
  667. }
  668. manifest = new Manifest(in);
  669. } catch (IOException e) {
  670. throw new BuildException ("Unable to read manifest", e, getLocation());
  671. } finally {
  672. if (in != null) {
  673. in.close();
  674. }
  675. }
  676. // Create the streams necessary to write the jarfile
  677. jarStream = new JarOutputStream(new FileOutputStream(jarfile), manifest);
  678. jarStream.setMethod(JarOutputStream.DEFLATED);
  679. // Loop through all the class files found and add them to the jar
  680. for (Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext();) {
  681. String entryName = (String) entryIterator.next();
  682. if (entryName.equals(MANIFEST)) {
  683. continue;
  684. }
  685. File entryFile = (File) files.get(entryName);
  686. log("adding file '" + entryName + "'",
  687. Project.MSG_VERBOSE);
  688. addFileToJar(jarStream, entryFile, entryName);
  689. // See if there are any inner classes for this class and add them in if there are
  690. InnerClassFilenameFilter flt = new InnerClassFilenameFilter(entryFile.getName());
  691. File entryDir = entryFile.getParentFile();
  692. String[] innerfiles = entryDir.list(flt);
  693. if (innerfiles != null) {
  694. for (int i = 0, n = innerfiles.length; i < n; i++) {
  695. //get and clean up innerclass name
  696. int entryIndex = entryName.lastIndexOf(entryFile.getName()) - 1;
  697. if (entryIndex < 0) {
  698. entryName = innerfiles[i];
  699. } else {
  700. entryName = entryName.substring(0, entryIndex)
  701. + File.separatorChar + innerfiles[i];
  702. }
  703. // link the file
  704. entryFile = new File(config.srcDir, entryName);
  705. log("adding innerclass file '" + entryName + "'",
  706. Project.MSG_VERBOSE);
  707. addFileToJar(jarStream, entryFile, entryName);
  708. }
  709. }
  710. }
  711. } catch (IOException ioe) {
  712. String msg = "IOException while processing ejb-jar file '"
  713. + jarfile.toString()
  714. + "'. Details: "
  715. + ioe.getMessage();
  716. throw new BuildException(msg, ioe);
  717. } finally {
  718. if (jarStream != null) {
  719. try {
  720. jarStream.close();
  721. } catch (IOException closeException) {
  722. // ignore
  723. }
  724. }
  725. }
  726. } // end of writeJar
  727. /**
  728. * Add all available classes, that depend on Remote, Home, Bean, PK
  729. * @param checkEntries files, that are extracted from the deployment descriptor
  730. */
  731. protected void checkAndAddDependants(Hashtable checkEntries)
  732. throws BuildException {
  733. if (dependencyAnalyzer == null) {
  734. return;
  735. }
  736. dependencyAnalyzer.reset();
  737. Iterator i = checkEntries.keySet().iterator();
  738. while (i.hasNext()) {
  739. String entryName = (String) i.next();
  740. if (entryName.endsWith(".class")) {
  741. String className = entryName.substring(0,
  742. entryName.length() - ".class".length());
  743. className = className.replace(File.separatorChar, '/');
  744. className = className.replace('/', '.');
  745. dependencyAnalyzer.addRootClass(className);
  746. }
  747. }
  748. Enumeration e = dependencyAnalyzer.getClassDependencies();
  749. while (e.hasMoreElements()) {
  750. String classname = (String) e.nextElement();
  751. String location
  752. = classname.replace('.', File.separatorChar) + ".class";
  753. File classFile = new File(config.srcDir, location);
  754. if (classFile.exists()) {
  755. checkEntries.put(location, classFile);
  756. log("dependent class: " + classname + " - " + classFile,
  757. Project.MSG_VERBOSE);
  758. }
  759. }
  760. }
  761. /**
  762. * Returns a Classloader object which parses the passed in generic EjbJar classpath.
  763. * The loader is used to dynamically load classes from javax.ejb.* and the classes
  764. * being added to the jar.
  765. *
  766. */
  767. protected ClassLoader getClassLoaderForBuild() {
  768. if (classpathLoader != null) {
  769. return classpathLoader;
  770. }
  771. Path combinedClasspath = getCombinedClasspath();
  772. // only generate a new ClassLoader if we have a classpath
  773. if (combinedClasspath == null) {
  774. classpathLoader = getClass().getClassLoader();
  775. } else {
  776. classpathLoader
  777. = getTask().getProject().createClassLoader(combinedClasspath);
  778. }
  779. return classpathLoader;
  780. }
  781. /**
  782. * Called to validate that the tool parameters have been configured.
  783. *
  784. * @throws BuildException If the Deployment Tool's configuration isn't
  785. * valid
  786. */
  787. public void validateConfigured() throws BuildException {
  788. if ((destDir == null) || (!destDir.isDirectory())) {
  789. String msg = "A valid destination directory must be specified "
  790. + "using the \"destdir\" attribute.";
  791. throw new BuildException(msg, getLocation());
  792. }
  793. }
  794. }