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