001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.optional.ejb;
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.util.Enumeration;
026: import java.util.HashSet;
027: import java.util.Hashtable;
028: import java.util.Iterator;
029: import java.util.Set;
030: import java.util.jar.JarOutputStream;
031: import java.util.jar.Manifest;
032: import java.util.zip.ZipEntry;
033:
034: import javax.xml.parsers.SAXParser;
035:
036: import org.apache.tools.ant.BuildException;
037: import org.apache.tools.ant.DirectoryScanner;
038: import org.apache.tools.ant.Location;
039: import org.apache.tools.ant.Project;
040: import org.apache.tools.ant.Task;
041: import org.apache.tools.ant.types.FileSet;
042: import org.apache.tools.ant.types.Path;
043: import org.apache.tools.ant.util.depend.DependencyAnalyzer;
044: import org.xml.sax.InputSource;
045: import org.xml.sax.SAXException;
046:
047: /**
048: * A deployment tool which creates generic EJB jars. Generic jars contains
049: * only those classes and META-INF entries specified in the EJB 1.1 standard
050: *
051: * This class is also used as a framework for the creation of vendor specific
052: * deployment tools. A number of template methods are provided through which the
053: * vendor specific tool can hook into the EJB creation process.
054: *
055: */
056: public class GenericDeploymentTool implements EJBDeploymentTool {
057: /** The default buffer byte size to use for IO */
058: public static final int DEFAULT_BUFFER_SIZE = 1024;
059: /** The level to use for compression */
060: public static final int JAR_COMPRESS_LEVEL = 9;
061:
062: /** The standard META-INF directory in jar files */
063: protected static final String META_DIR = "META-INF/";
064:
065: /** The standard MANIFEST file */
066: protected static final String MANIFEST = META_DIR + "MANIFEST.MF";
067:
068: /** Name for EJB Deployment descriptor within EJB jars */
069: protected static final String EJB_DD = "ejb-jar.xml";
070:
071: /** A dependency analyzer name to find ancestor classes */
072: public static final String ANALYZER_SUPER = "super";
073: /** A dependency analyzer name to find all related classes */
074: public static final String ANALYZER_FULL = "full";
075: /** A dependency analyzer name for no analyzer */
076: public static final String ANALYZER_NONE = "none";
077:
078: /** The default analyzer */
079: public static final String DEFAULT_ANALYZER = ANALYZER_SUPER;
080:
081: /** The analyzer class for the super analyzer */
082: public static final String ANALYZER_CLASS_SUPER = "org.apache.tools.ant.util.depend.bcel.AncestorAnalyzer";
083: /** The analyzer class for the super analyzer */
084: public static final String ANALYZER_CLASS_FULL = "org.apache.tools.ant.util.depend.bcel.FullAnalyzer";
085:
086: /**
087: * The configuration from the containing task. This config combined
088: * with the settings of the individual attributes here constitues the
089: * complete config for this deployment tool.
090: */
091: private EjbJar.Config config;
092:
093: /** Stores a handle to the directory to put the Jar files in */
094: private File destDir;
095:
096: /** The classpath to use with this deployment tool. This is appended to
097: any paths from the ejbjar task itself.*/
098: private Path classpath;
099:
100: /** Instance variable that stores the suffix for the generated jarfile. */
101: private String genericJarSuffix = "-generic.jar";
102:
103: /**
104: * The task to which this tool belongs. This is used to access services
105: * provided by the ant core, such as logging.
106: */
107: private Task task;
108:
109: /**
110: * The classloader generated from the given classpath to load
111: * the super classes and super interfaces.
112: */
113: private ClassLoader classpathLoader = null;
114:
115: /**
116: * Set of files have been loaded into the EJB jar
117: */
118: private Set addedfiles;
119:
120: /**
121: * Handler used to parse the EJB XML descriptor
122: */
123: private DescriptorHandler handler;
124:
125: /**
126: * Dependency analyzer used to collect class dependencies
127: */
128: private DependencyAnalyzer dependencyAnalyzer;
129:
130: /** No arg constructor */
131: public GenericDeploymentTool() {
132: }
133:
134: /**
135: * Set the destination directory; required.
136: * @param inDir the destination directory.
137: */
138: public void setDestdir(File inDir) {
139: this .destDir = inDir;
140: }
141:
142: /**
143: * Get the destination directory.
144: *
145: * @return the destination directory into which EJB jars are to be written
146: */
147: protected File getDestDir() {
148: return destDir;
149: }
150:
151: /**
152: * Set the task which owns this tool
153: *
154: * @param task the Task to which this deployment tool is associated.
155: */
156: public void setTask(Task task) {
157: this .task = task;
158: }
159:
160: /**
161: * Get the task for this tool.
162: *
163: * @return the Task instance this tool is associated with.
164: */
165: protected Task getTask() {
166: return task;
167: }
168:
169: /**
170: * Get the basename terminator.
171: *
172: * @return an ejbjar task configuration
173: */
174: protected EjbJar.Config getConfig() {
175: return config;
176: }
177:
178: /**
179: * Indicate if this build is using the base jar name.
180: *
181: * @return true if the name of the generated jar is coming from the
182: * basejarname attribute
183: */
184: protected boolean usingBaseJarName() {
185: return config.baseJarName != null;
186: }
187:
188: /**
189: * Set the suffix for the generated jar file.
190: * @param inString the string to use as the suffix.
191: */
192: public void setGenericJarSuffix(String inString) {
193: this .genericJarSuffix = inString;
194: }
195:
196: /**
197: * Add the classpath for the user classes
198: *
199: * @return a Path instance to be configured by Ant.
200: */
201: public Path createClasspath() {
202: if (classpath == null) {
203: classpath = new Path(task.getProject());
204: }
205: return classpath.createPath();
206: }
207:
208: /**
209: * Set the classpath to be used for this compilation.
210: *
211: * @param classpath the classpath to be used for this build.
212: */
213: public void setClasspath(Path classpath) {
214: this .classpath = classpath;
215: }
216:
217: /**
218: * Get the classpath by combining the one from the surrounding task, if any
219: * and the one from this tool.
220: *
221: * @return the combined classpath
222: */
223: protected Path getCombinedClasspath() {
224: Path combinedPath = classpath;
225: if (config.classpath != null) {
226: if (combinedPath == null) {
227: combinedPath = config.classpath;
228: } else {
229: combinedPath.append(config.classpath);
230: }
231: }
232:
233: return combinedPath;
234: }
235:
236: /**
237: * Log a message to the Ant output.
238: *
239: * @param message the message to be logged.
240: * @param level the severity of this message.
241: */
242: protected void log(String message, int level) {
243: getTask().log(message, level);
244: }
245:
246: /**
247: * Get the build file location associated with this element's task.
248: *
249: * @return the task's location instance.
250: */
251: protected Location getLocation() {
252: return getTask().getLocation();
253: }
254:
255: private void createAnalyzer() {
256: String analyzer = config.analyzer;
257: if (analyzer == null) {
258: analyzer = DEFAULT_ANALYZER;
259: }
260:
261: if (analyzer.equals(ANALYZER_NONE)) {
262: return;
263: }
264:
265: String analyzerClassName = null;
266: if (analyzer.equals(ANALYZER_SUPER)) {
267: analyzerClassName = ANALYZER_CLASS_SUPER;
268: } else if (analyzer.equals(ANALYZER_FULL)) {
269: analyzerClassName = ANALYZER_CLASS_FULL;
270: } else {
271: analyzerClassName = analyzer;
272: }
273:
274: try {
275: Class analyzerClass = Class.forName(analyzerClassName);
276: dependencyAnalyzer = (DependencyAnalyzer) analyzerClass
277: .newInstance();
278: dependencyAnalyzer.addClassPath(new Path(task.getProject(),
279: config.srcDir.getPath()));
280: dependencyAnalyzer.addClassPath(config.classpath);
281: } catch (NoClassDefFoundError e) {
282: dependencyAnalyzer = null;
283: task.log(
284: "Unable to load dependency analyzer: "
285: + analyzerClassName
286: + " - dependent class not found: "
287: + e.getMessage(), Project.MSG_WARN);
288: } catch (Exception e) {
289: dependencyAnalyzer = null;
290: task.log("Unable to load dependency analyzer: "
291: + analyzerClassName + " - exception: "
292: + e.getMessage(), Project.MSG_WARN);
293: }
294: }
295:
296: /**
297: * Configure this tool for use in the ejbjar task.
298: *
299: * @param config the configuration from the surrounding ejbjar task.
300: */
301: public void configure(EjbJar.Config config) {
302: this .config = config;
303:
304: createAnalyzer();
305: classpathLoader = null;
306: }
307:
308: /**
309: * Utility method that encapsulates the logic of adding a file entry to
310: * a .jar file. Used by execute() to add entries to the jar file as it is
311: * constructed.
312: * @param jStream A JarOutputStream into which to write the
313: * jar entry.
314: * @param inputFile A File from which to read the
315: * contents the file being added.
316: * @param logicalFilename A String representing the name, including
317: * all relevant path information, that should be stored for the entry
318: * being added.
319: * @throws BuildException if there is a problem.
320: */
321: protected void addFileToJar(JarOutputStream jStream,
322: File inputFile, String logicalFilename)
323: throws BuildException {
324: FileInputStream iStream = null;
325: try {
326: if (!addedfiles.contains(logicalFilename)) {
327: iStream = new FileInputStream(inputFile);
328: // Create the zip entry and add it to the jar file
329: ZipEntry zipEntry = new ZipEntry(logicalFilename
330: .replace('\\', '/'));
331: jStream.putNextEntry(zipEntry);
332:
333: // Create the file input stream, and buffer everything over
334: // to the jar output stream
335: byte[] byteBuffer = new byte[2 * DEFAULT_BUFFER_SIZE];
336: int count = 0;
337: do {
338: jStream.write(byteBuffer, 0, count);
339: count = iStream.read(byteBuffer, 0,
340: byteBuffer.length);
341: } while (count != -1);
342:
343: //add it to list of files in jar
344: addedfiles.add(logicalFilename);
345: }
346: } catch (IOException ioe) {
347: log(
348: "WARNING: IOException while adding entry "
349: + logicalFilename + " to jarfile from "
350: + inputFile.getPath() + " "
351: + ioe.getClass().getName() + "-"
352: + ioe.getMessage(), Project.MSG_WARN);
353: } finally {
354: // Close up the file input stream for the class file
355: if (iStream != null) {
356: try {
357: iStream.close();
358: } catch (IOException closeException) {
359: // ignore
360: }
361: }
362: }
363: }
364:
365: /**
366: * Get a descriptionHandler.
367: * @param srcDir the source directory.
368: * @return a handler.
369: */
370: protected DescriptorHandler getDescriptorHandler(File srcDir) {
371: DescriptorHandler h = new DescriptorHandler(getTask(), srcDir);
372:
373: registerKnownDTDs(h);
374:
375: // register any DTDs supplied by the user
376: for (Iterator i = getConfig().dtdLocations.iterator(); i
377: .hasNext();) {
378: EjbJar.DTDLocation dtdLocation = (EjbJar.DTDLocation) i
379: .next();
380: h.registerDTD(dtdLocation.getPublicId(), dtdLocation
381: .getLocation());
382: }
383: return h;
384: }
385:
386: /**
387: * Register the locations of all known DTDs.
388: *
389: * vendor-specific subclasses should override this method to define
390: * the vendor-specific locations of the EJB DTDs
391: * @param handler no used in this class.
392: */
393: protected void registerKnownDTDs(DescriptorHandler handler) {
394: // none to register for generic
395: }
396:
397: /** {@inheritDoc}. */
398: public void processDescriptor(String descriptorFileName,
399: SAXParser saxParser) {
400:
401: checkConfiguration(descriptorFileName, saxParser);
402:
403: try {
404: handler = getDescriptorHandler(config.srcDir);
405:
406: // Retrive the files to be added to JAR from EJB descriptor
407: Hashtable ejbFiles = parseEjbFiles(descriptorFileName,
408: saxParser);
409:
410: // Add any support classes specified in the build file
411: addSupportClasses(ejbFiles);
412:
413: // Determine the JAR filename (without filename extension)
414: String baseName = getJarBaseName(descriptorFileName);
415:
416: String ddPrefix = getVendorDDPrefix(baseName,
417: descriptorFileName);
418:
419: File manifestFile = getManifestFile(ddPrefix);
420: if (manifestFile != null) {
421: ejbFiles.put(MANIFEST, manifestFile);
422: }
423:
424: // First the regular deployment descriptor
425: ejbFiles.put(META_DIR + EJB_DD, new File(
426: config.descriptorDir, descriptorFileName));
427:
428: // now the vendor specific files, if any
429: addVendorFiles(ejbFiles, ddPrefix);
430:
431: // add any dependent files
432: checkAndAddDependants(ejbFiles);
433:
434: // Lastly create File object for the Jar files. If we are using
435: // a flat destination dir, then we need to redefine baseName!
436: if (config.flatDestDir && baseName.length() != 0) {
437: int startName = baseName.lastIndexOf(File.separator);
438: if (startName == -1) {
439: startName = 0;
440: }
441:
442: int endName = baseName.length();
443: baseName = baseName.substring(startName, endName);
444: }
445:
446: File jarFile = getVendorOutputJarFile(baseName);
447:
448: // Check to see if we need a build and start doing the work!
449: if (needToRebuild(ejbFiles, jarFile)) {
450: // Log that we are going to build...
451: log("building " + jarFile.getName() + " with "
452: + String.valueOf(ejbFiles.size()) + " files",
453: Project.MSG_INFO);
454:
455: // Use helper method to write the jarfile
456: String publicId = getPublicId();
457: writeJar(baseName, jarFile, ejbFiles, publicId);
458:
459: } else {
460: // Log that the file is up to date...
461: log(jarFile.toString() + " is up to date.",
462: Project.MSG_VERBOSE);
463: }
464:
465: } catch (SAXException se) {
466: String msg = "SAXException while parsing '"
467: + descriptorFileName
468: + "'. This probably indicates badly-formed XML."
469: + " Details: " + se.getMessage();
470: throw new BuildException(msg, se);
471: } catch (IOException ioe) {
472: String msg = "IOException while parsing'"
473: + descriptorFileName.toString()
474: + "'. This probably indicates that the descriptor"
475: + " doesn't exist. Details: " + ioe.getMessage();
476: throw new BuildException(msg, ioe);
477: }
478: }
479:
480: /**
481: * This method is called as the first step in the processDescriptor method
482: * to allow vendor-specific subclasses to validate the task configuration
483: * prior to processing the descriptor. If the configuration is invalid,
484: * a BuildException should be thrown.
485: *
486: * @param descriptorFileName String representing the file name of an EJB
487: * descriptor to be processed
488: * @param saxParser SAXParser which may be used to parse the XML
489: * descriptor
490: * @throws BuildException if there is a problem.
491: */
492: protected void checkConfiguration(String descriptorFileName,
493: SAXParser saxParser) throws BuildException {
494:
495: /*
496: * For the GenericDeploymentTool, do nothing. Vendor specific
497: * subclasses should throw a BuildException if the configuration is
498: * invalid for their server.
499: */
500: }
501:
502: /**
503: * This method returns a list of EJB files found when the specified EJB
504: * descriptor is parsed and processed.
505: *
506: * @param descriptorFileName String representing the file name of an EJB
507: * descriptor to be processed
508: * @param saxParser SAXParser which may be used to parse the XML
509: * descriptor
510: * @return Hashtable of EJB class (and other) files to be
511: * added to the completed JAR file
512: * @throws SAXException Any SAX exception, possibly wrapping another
513: * exception
514: * @throws IOException An IOException from the parser, possibly from a
515: * the byte stream or character stream
516: */
517: protected Hashtable parseEjbFiles(String descriptorFileName,
518: SAXParser saxParser) throws IOException, SAXException {
519: FileInputStream descriptorStream = null;
520: Hashtable ejbFiles = null;
521:
522: try {
523:
524: /* Parse the ejb deployment descriptor. While it may not
525: * look like much, we use a SAXParser and an inner class to
526: * get hold of all the classfile names for the descriptor.
527: */
528: descriptorStream = new FileInputStream(new File(
529: config.descriptorDir, descriptorFileName));
530: saxParser.parse(new InputSource(descriptorStream), handler);
531:
532: ejbFiles = handler.getFiles();
533:
534: } finally {
535: if (descriptorStream != null) {
536: try {
537: descriptorStream.close();
538: } catch (IOException closeException) {
539: // ignore
540: }
541: }
542: }
543:
544: return ejbFiles;
545: }
546:
547: /**
548: * Adds any classes the user specifies using <i>support</i> nested elements
549: * to the <code>ejbFiles</code> Hashtable.
550: *
551: * @param ejbFiles Hashtable of EJB classes (and other) files that will be
552: * added to the completed JAR file
553: */
554: protected void addSupportClasses(Hashtable ejbFiles) {
555: // add in support classes if any
556: Project project = task.getProject();
557: for (Iterator i = config.supportFileSets.iterator(); i
558: .hasNext();) {
559: FileSet supportFileSet = (FileSet) i.next();
560: File supportBaseDir = supportFileSet.getDir(project);
561: DirectoryScanner supportScanner = supportFileSet
562: .getDirectoryScanner(project);
563: supportScanner.scan();
564: String[] supportFiles = supportScanner.getIncludedFiles();
565: for (int j = 0; j < supportFiles.length; ++j) {
566: ejbFiles.put(supportFiles[j], new File(supportBaseDir,
567: supportFiles[j]));
568: }
569: }
570: }
571:
572: /**
573: * Using the EJB descriptor file name passed from the <code>ejbjar</code>
574: * task, this method returns the "basename" which will be used to name the
575: * completed JAR file.
576: *
577: * @param descriptorFileName String representing the file name of an EJB
578: * descriptor to be processed
579: * @return The "basename" which will be used to name the
580: * completed JAR file
581: */
582: protected String getJarBaseName(String descriptorFileName) {
583:
584: String baseName = "";
585:
586: // Work out what the base name is
587: if (config.namingScheme.getValue().equals(
588: EjbJar.NamingScheme.BASEJARNAME)) {
589: String canonicalDescriptor = descriptorFileName.replace(
590: '\\', '/');
591: int index = canonicalDescriptor.lastIndexOf('/');
592: if (index != -1) {
593: baseName = descriptorFileName.substring(0, index + 1);
594: }
595: baseName += config.baseJarName;
596: } else if (config.namingScheme.getValue().equals(
597: EjbJar.NamingScheme.DESCRIPTOR)) {
598: int lastSeparatorIndex = descriptorFileName
599: .lastIndexOf(File.separator);
600: int endBaseName = -1;
601: if (lastSeparatorIndex != -1) {
602: endBaseName = descriptorFileName.indexOf(
603: config.baseNameTerminator, lastSeparatorIndex);
604: } else {
605: endBaseName = descriptorFileName
606: .indexOf(config.baseNameTerminator);
607: }
608:
609: if (endBaseName != -1) {
610: baseName = descriptorFileName.substring(0, endBaseName);
611: } else {
612: throw new BuildException(
613: "Unable to determine jar name "
614: + "from descriptor \""
615: + descriptorFileName + "\"");
616: }
617: } else if (config.namingScheme.getValue().equals(
618: EjbJar.NamingScheme.DIRECTORY)) {
619: File descriptorFile = new File(config.descriptorDir,
620: descriptorFileName);
621: String path = descriptorFile.getAbsolutePath();
622: int lastSeparatorIndex = path.lastIndexOf(File.separator);
623: if (lastSeparatorIndex == -1) {
624: throw new BuildException(
625: "Unable to determine directory name holding descriptor");
626: }
627: String dirName = path.substring(0, lastSeparatorIndex);
628: int dirSeparatorIndex = dirName.lastIndexOf(File.separator);
629: if (dirSeparatorIndex != -1) {
630: dirName = dirName.substring(dirSeparatorIndex + 1);
631: }
632:
633: baseName = dirName;
634: } else if (config.namingScheme.getValue().equals(
635: EjbJar.NamingScheme.EJB_NAME)) {
636: baseName = handler.getEjbName();
637: }
638: return baseName;
639: }
640:
641: /**
642: * Get the prefix for vendor deployment descriptors.
643: *
644: * This will contain the path and the start of the descriptor name,
645: * depending on the naming scheme
646: * @param baseName the base name to use.
647: * @param descriptorFileName the file name to use.
648: * @return the prefix.
649: */
650: public String getVendorDDPrefix(String baseName,
651: String descriptorFileName) {
652: String ddPrefix = null;
653:
654: if (config.namingScheme.getValue().equals(
655: EjbJar.NamingScheme.DESCRIPTOR)) {
656: ddPrefix = baseName + config.baseNameTerminator;
657: } else if (config.namingScheme.getValue().equals(
658: EjbJar.NamingScheme.BASEJARNAME)
659: || config.namingScheme.getValue().equals(
660: EjbJar.NamingScheme.EJB_NAME)
661: || config.namingScheme.getValue().equals(
662: EjbJar.NamingScheme.DIRECTORY)) {
663: String canonicalDescriptor = descriptorFileName.replace(
664: '\\', '/');
665: int index = canonicalDescriptor.lastIndexOf('/');
666: if (index == -1) {
667: ddPrefix = "";
668: } else {
669: ddPrefix = descriptorFileName.substring(0, index + 1);
670: }
671: }
672: return ddPrefix;
673: }
674:
675: /**
676: * Add any vendor specific files which should be included in the
677: * EJB Jar.
678: * @param ejbFiles a hashtable entryname -> file.
679: * @param ddPrefix a prefix to use.
680: */
681: protected void addVendorFiles(Hashtable ejbFiles, String ddPrefix) {
682: // nothing to add for generic tool.
683: }
684:
685: /**
686: * Get the vendor specific name of the Jar that will be output. The modification date
687: * of this jar will be checked against the dependent bean classes.
688: * @param baseName the basename to use.
689: */
690: File getVendorOutputJarFile(String baseName) {
691: return new File(destDir, baseName + genericJarSuffix);
692: }
693:
694: /**
695: * This method checks the timestamp on each file listed in the <code>
696: * ejbFiles</code> and compares them to the timestamp on the <code>jarFile
697: * </code>. If the <code>jarFile</code>'s timestamp is more recent than
698: * each EJB file, <code>true</code> is returned. Otherwise, <code>false
699: * </code> is returned.
700: * TODO: find a way to check the manifest-file, that is found by naming convention
701: *
702: * @param ejbFiles Hashtable of EJB classes (and other) files that will be
703: * added to the completed JAR file
704: * @param jarFile JAR file which will contain all of the EJB classes (and
705: * other) files
706: * @return boolean indicating whether or not the <code>jarFile</code>
707: * is up to date
708: */
709: protected boolean needToRebuild(Hashtable ejbFiles, File jarFile) {
710: if (jarFile.exists()) {
711: long lastBuild = jarFile.lastModified();
712:
713: Iterator fileIter = ejbFiles.values().iterator();
714:
715: // Loop through the files seeing if any has been touched
716: // more recently than the destination jar.
717: while (fileIter.hasNext()) {
718: File currentFile = (File) fileIter.next();
719: if (lastBuild < currentFile.lastModified()) {
720: log("Build needed because " + currentFile.getPath()
721: + " is out of date", Project.MSG_VERBOSE);
722: return true;
723: }
724: }
725: return false;
726: }
727:
728: return true;
729: }
730:
731: /**
732: * Returns the Public ID of the DTD specified in the EJB descriptor. Not
733: * every vendor-specific <code>DeploymentTool</code> will need to reference
734: * this value or may want to determine this value in a vendor-specific way.
735: *
736: * @return Public ID of the DTD specified in the EJB descriptor.
737: */
738: protected String getPublicId() {
739: return handler.getPublicId();
740: }
741:
742: /**
743: * Get the manifets file to use for building the generic jar.
744: *
745: * If the file does not exist the global manifest from the config is used
746: * otherwise the default Ant manifest will be used.
747: *
748: * @param prefix the prefix where to llook for the manifest file based on
749: * the naming convention.
750: *
751: * @return the manifest file or null if the manifest file does not exist
752: */
753: protected File getManifestFile(String prefix) {
754: File manifestFile = new File(getConfig().descriptorDir, prefix
755: + "manifest.mf");
756: if (manifestFile.exists()) {
757: return manifestFile;
758: }
759:
760: if (config.manifest != null) {
761: return config.manifest;
762: }
763: return null;
764: }
765:
766: /**
767: * Method used to encapsulate the writing of the JAR file. Iterates over the
768: * filenames/java.io.Files in the Hashtable stored on the instance variable
769: * ejbFiles.
770: * @param baseName the base name to use.
771: * @param jarfile the jar file to write to.
772: * @param files the files to write to the jar.
773: * @param publicId the id to use.
774: * @throws BuildException if there is a problem.
775: */
776: protected void writeJar(String baseName, File jarfile,
777: Hashtable files, String publicId) throws BuildException {
778:
779: JarOutputStream jarStream = null;
780: try {
781: // clean the addedfiles set
782: if (addedfiles == null) {
783: addedfiles = new HashSet();
784: } else {
785: addedfiles.clear();
786: }
787:
788: /* If the jarfile already exists then whack it and recreate it.
789: * Should probably think of a more elegant way to handle this
790: * so that in case of errors we don't leave people worse off
791: * than when we started =)
792: */
793: if (jarfile.exists()) {
794: jarfile.delete();
795: }
796: jarfile.getParentFile().mkdirs();
797: jarfile.createNewFile();
798:
799: InputStream in = null;
800: Manifest manifest = null;
801: try {
802: File manifestFile = (File) files.get(MANIFEST);
803: if (manifestFile != null && manifestFile.exists()) {
804: in = new FileInputStream(manifestFile);
805: } else {
806: String defaultManifest = "/org/apache/tools/ant/defaultManifest.mf";
807: in = this .getClass().getResourceAsStream(
808: defaultManifest);
809: if (in == null) {
810: throw new BuildException("Could not find "
811: + "default manifest: "
812: + defaultManifest);
813: }
814: }
815:
816: manifest = new Manifest(in);
817: } catch (IOException e) {
818: throw new BuildException("Unable to read manifest", e,
819: getLocation());
820: } finally {
821: if (in != null) {
822: in.close();
823: }
824: }
825:
826: // Create the streams necessary to write the jarfile
827:
828: jarStream = new JarOutputStream(new FileOutputStream(
829: jarfile), manifest);
830: jarStream.setMethod(JarOutputStream.DEFLATED);
831:
832: // Loop through all the class files found and add them to the jar
833: for (Iterator entryIterator = files.keySet().iterator(); entryIterator
834: .hasNext();) {
835: String entryName = (String) entryIterator.next();
836: if (entryName.equals(MANIFEST)) {
837: continue;
838: }
839:
840: File entryFile = (File) files.get(entryName);
841:
842: log("adding file '" + entryName + "'",
843: Project.MSG_VERBOSE);
844:
845: addFileToJar(jarStream, entryFile, entryName);
846:
847: // See if there are any inner classes for this class and add them in if there are
848: InnerClassFilenameFilter flt = new InnerClassFilenameFilter(
849: entryFile.getName());
850: File entryDir = entryFile.getParentFile();
851: String[] innerfiles = entryDir.list(flt);
852: if (innerfiles != null) {
853: for (int i = 0, n = innerfiles.length; i < n; i++) {
854:
855: //get and clean up innerclass name
856: int entryIndex = entryName
857: .lastIndexOf(entryFile.getName()) - 1;
858: if (entryIndex < 0) {
859: entryName = innerfiles[i];
860: } else {
861: entryName = entryName.substring(0,
862: entryIndex)
863: + File.separatorChar
864: + innerfiles[i];
865: }
866: // link the file
867: entryFile = new File(config.srcDir, entryName);
868:
869: log("adding innerclass file '" + entryName
870: + "'", Project.MSG_VERBOSE);
871:
872: addFileToJar(jarStream, entryFile, entryName);
873:
874: }
875: }
876: }
877: } catch (IOException ioe) {
878: String msg = "IOException while processing ejb-jar file '"
879: + jarfile.toString() + "'. Details: "
880: + ioe.getMessage();
881: throw new BuildException(msg, ioe);
882: } finally {
883: if (jarStream != null) {
884: try {
885: jarStream.close();
886: } catch (IOException closeException) {
887: // ignore
888: }
889: }
890: }
891: } // end of writeJar
892:
893: /**
894: * Add all available classes, that depend on Remote, Home, Bean, PK
895: * @param checkEntries files, that are extracted from the deployment descriptor
896: * @throws BuildException if there is a problem.
897: */
898: protected void checkAndAddDependants(Hashtable checkEntries)
899: throws BuildException {
900:
901: if (dependencyAnalyzer == null) {
902: return;
903: }
904:
905: dependencyAnalyzer.reset();
906:
907: Iterator i = checkEntries.keySet().iterator();
908: while (i.hasNext()) {
909: String entryName = (String) i.next();
910: if (entryName.endsWith(".class")) {
911: String className = entryName.substring(0, entryName
912: .length()
913: - ".class".length());
914: className = className.replace(File.separatorChar, '/');
915: className = className.replace('/', '.');
916:
917: dependencyAnalyzer.addRootClass(className);
918: }
919: }
920:
921: Enumeration e = dependencyAnalyzer.getClassDependencies();
922:
923: while (e.hasMoreElements()) {
924: String classname = (String) e.nextElement();
925: String location = classname
926: .replace('.', File.separatorChar)
927: + ".class";
928: File classFile = new File(config.srcDir, location);
929: if (classFile.exists()) {
930: checkEntries.put(location, classFile);
931: log(
932: "dependent class: " + classname + " - "
933: + classFile, Project.MSG_VERBOSE);
934: }
935: }
936: }
937:
938: /**
939: * Returns a Classloader object which parses the passed in generic EjbJar classpath.
940: * The loader is used to dynamically load classes from javax.ejb.* and the classes
941: * being added to the jar.
942: * @return a classloader.
943: */
944: protected ClassLoader getClassLoaderForBuild() {
945: if (classpathLoader != null) {
946: return classpathLoader;
947: }
948:
949: Path combinedClasspath = getCombinedClasspath();
950:
951: // only generate a new ClassLoader if we have a classpath
952: if (combinedClasspath == null) {
953: classpathLoader = getClass().getClassLoader();
954: } else {
955: classpathLoader = getTask().getProject().createClassLoader(
956: combinedClasspath);
957: }
958:
959: return classpathLoader;
960: }
961:
962: /**
963: * Called to validate that the tool parameters have been configured.
964: *
965: * @throws BuildException If the Deployment Tool's configuration isn't
966: * valid
967: */
968: public void validateConfigured() throws BuildException {
969: if ((destDir == null) || (!destDir.isDirectory())) {
970: String msg = "A valid destination directory must be specified "
971: + "using the \"destdir\" attribute.";
972: throw new BuildException(msg, getLocation());
973: }
974: }
975: }
|