001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution, if
020: * any, must include the following acknowlegement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowlegement may appear in the software itself,
024: * if and wherever such third-party acknowlegements normally appear.
025: *
026: * 4. The names "Ant" and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache"
032: * nor may "Apache" appear in their names without prior written
033: * permission of the Apache Group.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: */
054:
055: package org.objectweb.jonas.ant;
056:
057: // Standard java imports
058: import java.io.File;
059: import java.util.ArrayList;
060: import java.util.Iterator;
061: import java.util.List;
062: import javax.xml.parsers.ParserConfigurationException;
063: import javax.xml.parsers.SAXParser;
064: import javax.xml.parsers.SAXParserFactory;
065: import org.apache.tools.ant.BuildException;
066: import org.apache.tools.ant.DirectoryScanner;
067: import org.apache.tools.ant.Project;
068: import org.apache.tools.ant.taskdefs.MatchingTask;
069: import org.apache.tools.ant.types.EnumeratedAttribute;
070: import org.apache.tools.ant.types.FileSet;
071: import org.apache.tools.ant.types.Path;
072: import org.xml.sax.SAXException;
073:
074: /**
075: * Provides automated EJB JAR file creation.
076: * <p>
077: * Extends the
078: * MatchingTask class provided in the default ant distribution to provide a
079: * directory scanning EJB jarfile generator.
080: * </p>
081: *
082: * <p>
083: * The task works by taking the deployment descriptors one at a time and
084: * parsing them to locate the names of the classes which should be placed in
085: * the jar. The classnames are translated to java.io.Files by replacing
086: * periods with File.separatorChar and resolving the generated filename as a
087: * relative path under the srcDir attribute. All necessary files are then
088: * assembled into a jarfile. One jarfile is constructed for each deployment
089: * descriptor found.
090: * </p>
091: *
092: * @author <a href="mailto:tfennell@sapient.com">Tim Fennell</a>
093: * @author Conor MacNeill
094: * @author <a href="mailto:rob@springwellfarms.ca">Rob van Oostrum</a>
095: * */
096: public class EjbJar extends MatchingTask {
097:
098: /**
099: * Inner class used to record information about the location of a local DTD
100: */
101: public static class DTDLocation extends
102: org.apache.tools.ant.types.DTDLocation {
103: }
104:
105: /**
106: * A class which contains the configuration state of the ejbjar task.
107: * This state is passed to the deployment tools for configuration
108: */
109: static class Config {
110: /**
111: * Stores a handle to the directory under which to search for class
112: * files
113: */
114: public File srcDir;
115:
116: /**
117: * Stores a handle to the directory under which to search for
118: * deployment descriptors
119: */
120: public File descriptorDir;
121:
122: /** Instance variable that marks the end of the 'basename' */
123: public String baseNameTerminator = "-";
124:
125: /** Stores a handle to the destination EJB Jar file */
126: public String baseJarName;
127:
128: /**
129: * Instance variable that determines whether to use a package structure
130: * of a flat directory as the destination for the jar files.
131: */
132: public boolean flatDestDir = false;
133:
134: /**
135: * The classpath to use when loading classes
136: */
137: public Path classpath;
138:
139: /**
140: * A Fileset of support classes
141: */
142: public List supportFileSets = new ArrayList();
143:
144: /**
145: * The list of configured DTD locations
146: */
147: public ArrayList dtdLocations = new ArrayList();
148:
149: /**
150: * The naming scheme used to determine the generated jar name
151: * from the descriptor information
152: */
153: public NamingScheme namingScheme;
154:
155: /**
156: * The Manifest file
157: */
158: public File manifest;
159:
160: /**
161: * The dependency analyzer to use to add additional classes to the jar
162: */
163: public String analyzer;
164: }
165:
166: /**
167: * An EnumeratedAttribute class for handling different EJB jar naming
168: * schemes
169: */
170: public static class NamingScheme extends EnumeratedAttribute {
171: /**
172: * Naming scheme where generated jar is determined from the ejb-name in
173: * the deployment descripor
174: */
175: public static final String EJB_NAME = "ejb-name";
176:
177: /**
178: * Naming scheme where the generated jar name is based on the
179: * name of the directory containing the deployment descriptor
180: */
181: public static final String DIRECTORY = "directory";
182:
183: /**
184: * Naming scheme where the generated jar name is based on the name of
185: * the deployment descriptor file
186: */
187: public static final String DESCRIPTOR = "descriptor";
188:
189: /**
190: * Naming scheme where the generated jar is named by the basejarname
191: * attribute
192: */
193: public static final String BASEJARNAME = "basejarname";
194:
195: /**
196: * Gets the values of the NamingScheme
197: *
198: * @return an array of the values of this attribute class.
199: */
200: public String[] getValues() {
201: return new String[] { EJB_NAME, DIRECTORY, DESCRIPTOR,
202: BASEJARNAME };
203: }
204: }
205:
206: /**
207: * CMP versions supported
208: * valid CMP versions are 1.0 and 2.0
209: * @since ant 1.6
210: */
211: public static class CMPVersion extends EnumeratedAttribute {
212: public static final String CMP1_0 = "1.0";
213: public static final String CMP2_0 = "2.0";
214:
215: public String[] getValues() {
216: return new String[] { CMP1_0, CMP2_0, };
217: }
218: }
219:
220: /**
221: * The config which is built by this task and used by the various deployment
222: * tools to access the configuration of the ejbjar task
223: */
224: private Config config = new Config();
225:
226: /**
227: * Stores a handle to the directory to put the Jar files in. This is
228: * only used by the generic deployment descriptor tool which is created
229: * if no other deployment descriptor tools are provided. Normally each
230: * deployment tool will specify the desitination dir itself.
231: */
232: private File destDir;
233:
234: /** Instance variable that stores the suffix for the generated jarfile. */
235: private String genericJarSuffix = "-generic.jar";
236:
237: /** Instance variable that stores the CMP version for the jboss jarfile. */
238: private String cmpVersion = CMPVersion.CMP1_0;
239:
240: /** The list of deployment tools we are going to run. */
241: private ArrayList deploymentTools = new ArrayList();
242:
243: /**
244: * Add a deployment tool to the list of deployment tools that will be
245: * processed
246: *
247: * @param deploymentTool a deployment tool instance to which descriptors
248: * will be passed for processing.
249: */
250: protected void addDeploymentTool(EJBDeploymentTool deploymentTool) {
251: deploymentTool.setTask(this );
252: deploymentTools.add(deploymentTool);
253: }
254:
255: /**
256: * Adds a deployment tool for JOnAS server.
257: *
258: * @return the deployment tool instance to be configured.
259: */
260: public JonasDeploymentTool createJonas() {
261: log("JOnAS deployment tools", Project.MSG_VERBOSE);
262:
263: JonasDeploymentTool tool = new JonasDeploymentTool();
264: addDeploymentTool(tool);
265: return tool;
266: }
267:
268: /**
269: * Adds to the classpath used to locate the super classes and
270: * interfaces of the classes that will make up the EJB JAR.
271: *
272: * @return the path to be configured.
273: */
274: public Path createClasspath() {
275: if (config.classpath == null) {
276: config.classpath = new Path(getProject());
277: }
278: return config.classpath.createPath();
279: }
280:
281: /**
282: * Create a DTD location record. This stores the location of a DTD. The
283: * DTD is identified by its public Id. The location may either be a file
284: * location or a resource location.
285: *
286: * @return the DTD location object to be configured by Ant
287: */
288: public DTDLocation createDTD() {
289: DTDLocation dtdLocation = new DTDLocation();
290: config.dtdLocations.add(dtdLocation);
291:
292: return dtdLocation;
293: }
294:
295: /**
296: * Adds a fileset for support elements.
297: *
298: * @return a fileset which can be populated with support files.
299: */
300: public FileSet createSupport() {
301: FileSet supportFileSet = new FileSet();
302: config.supportFileSets.add(supportFileSet);
303: return supportFileSet;
304: }
305:
306: /**
307: * Set the Manifest file to use when jarring. As of EJB 1.1, manifest
308: * files are no longer used to configure the EJB. However, they still
309: * have a vital importance if the EJB is intended to be packaged in an
310: * EAR file. By adding "Class-Path" settings to a Manifest file, the EJB
311: * can look for classes inside the EAR file itself, allowing for easier
312: * deployment. This is outlined in the J2EE specification, and all J2EE
313: * components are meant to support it.
314: *
315: * @param manifest the manifest to be used in the EJB jar
316: */
317: public void setManifest(File manifest) {
318: config.manifest = manifest;
319: }
320:
321: /**
322: * Sets the source directory, which is the directory that
323: * contains the classes that will be added to the EJB jar. Typically
324: * this will include the home and remote interfaces and the bean class.
325: *
326: * @param inDir the source directory.
327: */
328: public void setSrcdir(File inDir) {
329: config.srcDir = inDir;
330: }
331:
332: /**
333: * Set the descriptor directory. The descriptor directory contains the
334: * EJB deployment descriptors. These are XML files that declare the
335: * properties of a bean in a particular deployment scenario. Such
336: * properties include, for example, the transactional nature of the bean
337: * and the security access control to the bean's methods.
338: *
339: * @param inDir the directory containing the deployment descriptors.
340: */
341: public void setDescriptordir(File inDir) {
342: config.descriptorDir = inDir;
343: }
344:
345: /**
346: * Set the analyzer to use when adding in dependencies to the JAR.
347: *
348: * @param analyzer the name of the dependency analyzer or a class.
349: */
350: public void setDependency(String analyzer) {
351: config.analyzer = analyzer;
352: }
353:
354: /**
355: * Set the base name of the EJB JAR that is to be created if it is not
356: * to be determined from the name of the deployment descriptor files.
357: *
358: * @param inValue the basename that will be used when writing the jar
359: * file containing the EJB
360: */
361: public void setBasejarname(String inValue) {
362: config.baseJarName = inValue;
363: if (config.namingScheme == null) {
364: config.namingScheme = new NamingScheme();
365: config.namingScheme.setValue(NamingScheme.BASEJARNAME);
366: } else if (!config.namingScheme.getValue().equals(
367: NamingScheme.BASEJARNAME)) {
368: throw new BuildException(
369: "The basejarname attribute is not "
370: + "compatible with the "
371: + config.namingScheme.getValue()
372: + " naming scheme");
373: }
374: }
375:
376: /**
377: * Set the naming scheme used to determine the name of the generated jars
378: * from the deployment descriptor
379: *
380: * @param namingScheme the naming scheme to be used
381: */
382: public void setNaming(NamingScheme namingScheme) {
383: config.namingScheme = namingScheme;
384: if (!config.namingScheme.getValue().equals(
385: NamingScheme.BASEJARNAME)
386: && config.baseJarName != null) {
387: throw new BuildException(
388: "The basejarname attribute is not "
389: + "compatible with the "
390: + config.namingScheme.getValue()
391: + " naming scheme");
392: }
393: }
394:
395: /**
396: * Gets the destination directory.
397: *
398: * @return destination directory
399: * @since ant 1.6
400: */
401: public File getDestdir() {
402: return this .destDir;
403: }
404:
405: /**
406: * Set the destination directory. The EJB jar files will be written into
407: * this directory. The jar files that exist in this directory are also
408: * used when determining if the contents of the jar file have changed.
409: * Note that this parameter is only used if no deployment tools are
410: * specified. Typically each deployment tool will specify its own
411: * destination directory.
412: *
413: * @param inDir the destination directory in which to generate jars
414: */
415: public void setDestdir(File inDir) {
416: this .destDir = inDir;
417: }
418:
419: /**
420: * Gets the CMP version.
421: *
422: * @return CMP version
423: * @since ant 1.6
424: */
425: public String getCmpversion() {
426: return this .cmpVersion;
427: }
428:
429: /**
430: * Sets the CMP version.
431: *
432: * @param version CMP version.
433: * Must be either <code>1.0</code> or <code>2.0</code>.<br/>
434: * Default is <code>1.0</code>.<br/>
435: * Initially, only the JBoss implementation does something specific for CMP 2.0.<br/>
436: * @since ant 1.6
437: */
438: public void setCmpversion(CMPVersion version) {
439: this .cmpVersion = version.getValue();
440: }
441:
442: /**
443: * Set the classpath to use when resolving classes for inclusion in the jar.
444: *
445: * @param classpath the classpath to use.
446: */
447: public void setClasspath(Path classpath) {
448: config.classpath = classpath;
449: }
450:
451: /**
452: * Controls whether the
453: * destination JARs are written out in the destination directory with
454: * the same hierarchical structure from which the deployment descriptors
455: * have been read. If this is set to true the generated EJB jars are
456: * written into the root of the destination directory, otherwise they
457: * are written out in the same relative position as the deployment
458: * descriptors in the descriptor directory.
459: *
460: * @param inValue the new value of the flatdestdir flag.
461: */
462: public void setFlatdestdir(boolean inValue) {
463: config.flatDestDir = inValue;
464: }
465:
466: /**
467: * Set the suffix for the generated jar file. When generic jars are
468: * generated, they have a suffix which is appended to the the bean name
469: * to create the name of the jar file. Note that this suffix includes
470: * the extension fo te jar file and should therefore end with an
471: * appropriate extension such as .jar or .ear
472: *
473: * @param inString the string to use as the suffix.
474: */
475: public void setGenericjarsuffix(String inString) {
476: this .genericJarSuffix = inString;
477: }
478:
479: /**
480: * The string which terminates the bean name.
481: * The convention used by this task is
482: * that bean descriptors are named as the BeanName with some suffix. The
483: * baseNameTerminator string separates the bean name and the suffix and
484: * is used to determine the bean name.
485: *
486: * @param inValue a string which marks the end of the basename.
487: */
488: public void setBasenameterminator(String inValue) {
489: config.baseNameTerminator = inValue;
490: }
491:
492: /**
493: * Validate the config that has been configured from the build file
494: *
495: * @throws BuildException if the config is not valid
496: */
497: private void validateConfig() throws BuildException {
498: if (config.srcDir == null) {
499: throw new BuildException(
500: "The srcDir attribute must be specified");
501: }
502:
503: if (config.descriptorDir == null) {
504: config.descriptorDir = config.srcDir;
505: }
506:
507: if (config.namingScheme == null) {
508: config.namingScheme = new NamingScheme();
509: config.namingScheme.setValue(NamingScheme.DESCRIPTOR);
510: } else if (config.namingScheme.getValue().equals(
511: NamingScheme.BASEJARNAME)
512: && config.baseJarName == null) {
513: throw new BuildException("The basejarname attribute must "
514: + "be specified with the basejarname naming scheme");
515: }
516: }
517:
518: /**
519: * Invoked by Ant after the task is prepared, when it is ready to execute
520: * this task.
521: *
522: * This will configure all of the nested deployment tools to allow them to
523: * process the jar. If no deployment tools have been configured a generic
524: * tool is created to handle the jar.
525: *
526: * A parser is configured and then each descriptor found is passed to all
527: * the deployment tool elements for processing.
528: *
529: * @exception BuildException thrown whenever a problem is
530: * encountered that cannot be recovered from, to signal to ant
531: * that a major problem occurred within this task.
532: */
533: public void execute() throws BuildException {
534: validateConfig();
535:
536: if (deploymentTools.size() == 0) {
537: GenericDeploymentTool genericTool = new GenericDeploymentTool();
538: genericTool.setTask(this );
539: genericTool.setDestdir(destDir);
540: genericTool.setGenericJarSuffix(genericJarSuffix);
541: deploymentTools.add(genericTool);
542: }
543:
544: for (Iterator i = deploymentTools.iterator(); i.hasNext();) {
545: EJBDeploymentTool tool = (EJBDeploymentTool) i.next();
546: tool.configure(config);
547: tool.validateConfigured();
548: }
549:
550: try {
551: // Create the parser using whatever parser the system dictates
552: SAXParserFactory saxParserFactory = SAXParserFactory
553: .newInstance();
554: saxParserFactory.setValidating(true);
555: saxParserFactory.setNamespaceAware(true);
556: SAXParser saxParser = saxParserFactory.newSAXParser();
557:
558: DirectoryScanner ds = getDirectoryScanner(config.descriptorDir);
559: ds.scan();
560: String[] files = ds.getIncludedFiles();
561:
562: log(files.length + " deployment descriptors located.",
563: Project.MSG_VERBOSE);
564:
565: // Loop through the files. Each file represents one deployment
566: // descriptor, and hence one bean in our model.
567: for (int index = 0; index < files.length; ++index) {
568: // process the deployment descriptor in each tool
569: for (Iterator i = deploymentTools.iterator(); i
570: .hasNext();) {
571: EJBDeploymentTool tool = (EJBDeploymentTool) i
572: .next();
573: tool.processDescriptor(files[index], saxParser);
574: }
575: }
576: } catch (SAXException se) {
577: String msg = "SAXException while creating parser."
578: + " Details: " + se.getMessage();
579: throw new BuildException(msg, se);
580: } catch (ParserConfigurationException pce) {
581: String msg = "ParserConfigurationException while creating parser. "
582: + "Details: " + pce.getMessage();
583: throw new BuildException(msg, pce);
584: }
585: } // end of execute()
586:
587: }
|