2
* The Apache Software License, Version 1.1
4
* Copyright (c) 2000 The Apache Software Foundation. All rights
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
11
* 1. Redistributions of source code must retain the above copyright
12
* notice, this list of conditions and the following disclaimer.
14
* 2. Redistributions in binary form must reproduce the above copyright
15
* notice, this list of conditions and the following disclaimer in
16
* the documentation and/or other materials provided with the
19
* 3. The end-user documentation included with the redistribution, if
20
* any, must include the following acknowlegement:
21
* "This product includes software developed by the
22
* Apache Software Foundation (http://www.apache.org/)."
23
* Alternately, this acknowlegement may appear in the software itself,
24
* if and wherever such third-party acknowlegements normally appear.
26
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
27
* Foundation" must not be used to endorse or promote products derived
28
* from this software without prior written permission. For written
29
* permission, please contact apache@apache.org.
31
* 5. Products derived from this software may not be called "Apache"
32
* nor may "Apache" appear in their names without prior written
33
* permission of the Apache Group.
35
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47
* ====================================================================
49
* This software consists of voluntary contributions made by many
50
* individuals on behalf of the Apache Software Foundation. For more
51
* information on the Apache Software Foundation, please see
52
* <http://www.apache.org/>.
54
package org.apache.tools.ant.taskdefs.optional.ejb;
58
import java.util.jar.*;
59
import java.util.zip.*;
62
import javax.xml.parsers.SAXParser;
63
import org.xml.sax.InputSource;
64
import org.xml.sax.SAXException;
66
import org.apache.tools.ant.*;
67
import org.apache.tools.ant.types.*;
70
* A deployment tool which creates generic EJB jars. Generic jars contains
71
* only those classes and META-INF entries specified in the EJB 1.1 standard
73
* This class is also used as a framework for the creation of vendor specific
74
* deployment tools. A number of template methods are provided through which the
75
* vendor specific tool can hook into the EJB creation process.
77
public class GenericDeploymentTool implements EJBDeploymentTool {
78
/** Private constants that are used when constructing the standard jarfile */
79
protected static final String META_DIR = "META-INF/";
80
protected static final String EJB_DD = "ejb-jar.xml";
83
* The configuration from the containing task. This config combined with the
84
* settings of the individual attributes here constitues the complete config for
85
* this deployment tool.
87
private EjbJar.Config config;
89
/** Stores a handle to the directory to put the Jar files in */
92
/** The classpath to use with this deployment tool. This is appended to
93
any paths from the ejbjar task itself.*/
94
private Path classpath;
96
/** Instance variable that stores the suffix for the generated jarfile. */
97
private String genericJarSuffix = "-generic.jar";
100
* The task to which this tool belongs. This is used to access services provided
101
* by the ant core, such as logging.
106
* The classloader generated from the given classpath to load
107
* the super classes and super interfaces.
109
private ClassLoader classpathLoader = null;
112
* List of files have been loaded into the EJB jar
114
private List addedfiles;
117
* Handler used to parse the EJB XML descriptor
119
private DescriptorHandler handler;
122
* Setter used to store the value of destination directory prior to execute()
124
* @param inDir the destination directory.
126
public void setDestdir(File inDir) {
127
this.destDir = inDir;
131
* Get the desitination directory.
133
protected File getDestDir() {
139
* Set the task which owns this tool
141
public void setTask(Task task) {
146
* Get the task for this tool.
148
protected Task getTask() {
153
* Get the basename terminator.
155
protected EjbJar.Config getConfig() {
160
* Returns true, if the meta-inf dir is being explicitly set, false otherwise.
162
protected boolean usingBaseJarName() {
163
return config.baseJarName != null;
167
* Setter used to store the suffix for the generated jar file.
168
* @param inString the string to use as the suffix.
170
public void setGenericJarSuffix(String inString) {
171
this.genericJarSuffix = inString;
175
* Add the classpath for the user classes
177
public Path createClasspath() {
178
if (classpath == null) {
179
classpath = new Path(task.getProject());
181
return classpath.createPath();
185
* Set the classpath to be used for this compilation.
187
public void setClasspath(Path classpath) {
188
this.classpath = classpath;
192
* Get the classpath by combining the one from the surrounding task, if any
193
* and the one from tis tool.
195
protected Path getCombinedClasspath() {
196
Path combinedPath = classpath;
197
if (config.classpath != null) {
198
if (combinedPath == null) {
199
combinedPath = config.classpath;
202
combinedPath.append(config.classpath);
209
protected void log(String message, int level) {
210
getTask().log(message, level);
213
protected Location getLocation() {
214
return getTask().getLocation();
219
* Configure this tool for use in the ejbjar task.
221
public void configure(EjbJar.Config config) {
222
this.config = config;
224
classpathLoader = null;
228
* Utility method that encapsulates the logic of adding a file entry to
229
* a .jar file. Used by execute() to add entries to the jar file as it is
231
* @param jStream A JarOutputStream into which to write the
233
* @param inputFile A File from which to read the
234
* contents the file being added.
235
* @param logicalFilename A String representing the name, including
236
* all relevant path information, that should be stored for the entry
239
protected void addFileToJar(JarOutputStream jStream,
241
String logicalFilename)
242
throws BuildException {
243
FileInputStream iStream = null;
245
if (!addedfiles.contains(logicalFilename)) {
246
iStream = new FileInputStream(inputFile);
247
// Create the zip entry and add it to the jar file
248
ZipEntry zipEntry = new ZipEntry(logicalFilename.replace('\\','/'));
249
jStream.putNextEntry(zipEntry);
251
// Create the file input stream, and buffer everything over
252
// to the jar output stream
253
byte[] byteBuffer = new byte[2 * 1024];
256
jStream.write(byteBuffer, 0, count);
257
count = iStream.read(byteBuffer, 0, byteBuffer.length);
258
} while (count != -1);
260
//add it to list of files in jar
261
addedfiles.add(logicalFilename);
264
catch (IOException ioe) {
265
log("WARNING: IOException while adding entry " +
266
logicalFilename + " to jarfile from " + inputFile.getPath() + " " +
267
ioe.getClass().getName() + "-" + ioe.getMessage(), Project.MSG_WARN);
270
// Close up the file input stream for the class file
271
if (iStream != null) {
275
catch (IOException closeException) {}
280
protected DescriptorHandler getDescriptorHandler(File srcDir) {
281
DescriptorHandler handler = new DescriptorHandler(getTask(), srcDir);
283
registerKnownDTDs(handler);
285
// register any DTDs supplied by the user
286
for (Iterator i = getConfig().dtdLocations.iterator(); i.hasNext();) {
287
EjbJar.DTDLocation dtdLocation = (EjbJar.DTDLocation)i.next();
288
handler.registerDTD(dtdLocation.getPublicId(), dtdLocation.getLocation());
294
* Register the locations of all known DTDs.
296
* vendor-specific subclasses should override this method to define
297
* the vendor-specific locations of the EJB DTDs
299
protected void registerKnownDTDs(DescriptorHandler handler) {
300
// none to register for generic
303
public void processDescriptor(String descriptorFileName, SAXParser saxParser) {
305
checkConfiguration(descriptorFileName, saxParser);
308
handler = getDescriptorHandler(config.srcDir);
310
// Retrive the files to be added to JAR from EJB descriptor
311
Hashtable ejbFiles = parseEjbFiles(descriptorFileName, saxParser);
313
// Add any support classes specified in the build file
314
addSupportClasses(ejbFiles);
316
// Determine the JAR filename (without filename extension)
317
String baseName = getJarBaseName(descriptorFileName);
319
String ddPrefix = getVendorDDPrefix(baseName, descriptorFileName);
321
// First the regular deployment descriptor
322
ejbFiles.put(META_DIR + EJB_DD,
323
new File(config.descriptorDir, descriptorFileName));
325
// now the vendor specific files, if any
326
addVendorFiles(ejbFiles, ddPrefix);
328
// add any inherited files
329
checkAndAddInherited(ejbFiles);
331
// Lastly create File object for the Jar files. If we are using
332
// a flat destination dir, then we need to redefine baseName!
333
if (config.flatDestDir && baseName.length() != 0) {
334
int startName = baseName.lastIndexOf(File.separator);
335
if (startName == -1) {
339
int endName = baseName.length();
340
baseName = baseName.substring(startName, endName);
343
File jarFile = getVendorOutputJarFile(baseName);
346
// Check to see if we need a build and start doing the work!
347
if (needToRebuild(ejbFiles, jarFile)) {
348
// Log that we are going to build...
352
+ String.valueOf(ejbFiles.size())
356
// Use helper method to write the jarfile
357
String publicId = getPublicId();
358
writeJar(baseName, jarFile, ejbFiles, publicId);
362
// Log that the file is up to date...
363
log(jarFile.toString() + " is up to date.",
364
Project.MSG_VERBOSE);
368
catch (SAXException se) {
369
String msg = "SAXException while parsing '"
370
+ descriptorFileName.toString()
371
+ "'. This probably indicates badly-formed XML."
374
throw new BuildException(msg, se);
376
catch (IOException ioe) {
377
String msg = "IOException while parsing'"
378
+ descriptorFileName.toString()
379
+ "'. This probably indicates that the descriptor"
380
+ " doesn't exist. Details: "
382
throw new BuildException(msg, ioe);
387
* This method is called as the first step in the processDescriptor method
388
* to allow vendor-specific subclasses to validate the task configuration
389
* prior to processing the descriptor. If the configuration is invalid,
390
* a BuildException should be thrown.
392
* @param descriptorFileName String representing the file name of an EJB
393
* descriptor to be processed
394
* @param saxParser SAXParser which may be used to parse the XML
396
* @thows BuildException Thrown if the configuration is invalid
398
protected void checkConfiguration(String descriptorFileName,
399
SAXParser saxParser) throws BuildException {
402
* For the GenericDeploymentTool, do nothing. Vendor specific
403
* subclasses should throw a BuildException if the configuration is
404
* invalid for their server.
409
* This method returns a list of EJB files found when the specified EJB
410
* descriptor is parsed and processed.
412
* @param descriptorFileName String representing the file name of an EJB
413
* descriptor to be processed
414
* @param saxParser SAXParser which may be used to parse the XML
416
* @return Hashtable of EJB class (and other) files to be
417
* added to the completed JAR file
418
* @throws SAXException Any SAX exception, possibly wrapping another
420
* @throws IOException An IOException from the parser, possibly from a
421
* the byte stream or character stream
423
protected Hashtable parseEjbFiles(String descriptorFileName, SAXParser saxParser)
424
throws IOException, SAXException {
425
FileInputStream descriptorStream = null;
426
Hashtable ejbFiles = null;
430
/* Parse the ejb deployment descriptor. While it may not
431
* look like much, we use a SAXParser and an inner class to
432
* get hold of all the classfile names for the descriptor.
434
descriptorStream = new FileInputStream(new File(config.descriptorDir, descriptorFileName));
435
saxParser.parse(new InputSource(descriptorStream), handler);
437
ejbFiles = handler.getFiles();
440
if (descriptorStream != null) {
442
descriptorStream.close();
444
catch (IOException closeException) {}
452
* Adds any classes the user specifies using <i>support</i> nested elements
453
* to the <code>ejbFiles</code> Hashtable.
455
* @param ejbFiles Hashtable of EJB classes (and other) files that will be
456
* added to the completed JAR file
458
protected void addSupportClasses(Hashtable ejbFiles) {
459
// add in support classes if any
460
Project project = task.getProject();
461
for (Iterator i = config.supportFileSets.iterator(); i.hasNext();) {
462
FileSet supportFileSet = (FileSet)i.next();
463
File supportBaseDir = supportFileSet.getDir(project);
464
DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner(project);
465
supportScanner.scan();
466
String[] supportFiles = supportScanner.getIncludedFiles();
467
for (int j = 0; j < supportFiles.length; ++j) {
468
ejbFiles.put(supportFiles[j], new File(supportBaseDir, supportFiles[j]));
475
* Using the EJB descriptor file name passed from the <code>ejbjar</code>
476
* task, this method returns the "basename" which will be used to name the
477
* completed JAR file.
479
* @param descriptorFileName String representing the file name of an EJB
480
* descriptor to be processed
481
* @return The "basename" which will be used to name the
484
protected String getJarBaseName(String descriptorFileName) {
486
String baseName = "";
488
// Work out what the base name is
489
if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.BASEJARNAME)) {
490
String canonicalDescriptor = descriptorFileName.replace('\\', '/');
491
int index = canonicalDescriptor.lastIndexOf('/');
493
baseName = descriptorFileName.substring(0, index + 1);
495
baseName += config.baseJarName;
496
} else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {
497
int lastSeparatorIndex = descriptorFileName.lastIndexOf(File.separator);
498
int endBaseName = -1;
499
if (lastSeparatorIndex != -1) {
500
endBaseName = descriptorFileName.indexOf(config.baseNameTerminator,
503
endBaseName = descriptorFileName.indexOf(config.baseNameTerminator);
506
if (endBaseName != -1) {
507
baseName = descriptorFileName.substring(0, endBaseName);
509
baseName = descriptorFileName.substring(0, endBaseName);
510
} else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.DIRECTORY)) {
511
int lastSeparatorIndex = descriptorFileName.lastIndexOf(File.separator);
512
String dirName = descriptorFileName.substring(0, lastSeparatorIndex);
513
int dirSeparatorIndex = dirName.lastIndexOf(File.separator);
514
if (dirSeparatorIndex != -1) {
515
dirName = dirName.substring(dirSeparatorIndex + 1);
519
} else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.EJB_NAME)) {
520
baseName = handler.getEjbName();
526
* Get the prefix for vendor deployment descriptors.
528
* This will contain the path and the start of the descriptor name,
529
* depending on the naming scheme
531
public String getVendorDDPrefix(String baseName, String descriptorFileName) {
532
String ddPrefix = null;
534
if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.DESCRIPTOR)) {
535
ddPrefix = baseName + config.baseNameTerminator;
536
} else if (config.namingScheme.getValue().equals(EjbJar.NamingScheme.BASEJARNAME) ||
537
config.namingScheme.getValue().equals(EjbJar.NamingScheme.EJB_NAME) ||
538
config.namingScheme.getValue().equals(EjbJar.NamingScheme.DIRECTORY)) {
539
String canonicalDescriptor = descriptorFileName.replace('\\', '/');
540
int index = canonicalDescriptor.lastIndexOf('/');
545
ddPrefix = descriptorFileName.substring(0, index + 1);
552
* Add any vendor specific files which should be included in the
555
protected void addVendorFiles(Hashtable ejbFiles, String ddPrefix) {
556
// nothing to add for generic tool.
561
* Get the vendor specific name of the Jar that will be output. The modification date
562
* of this jar will be checked against the dependent bean classes.
564
File getVendorOutputJarFile(String baseName) {
565
return new File(destDir, baseName + genericJarSuffix);
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.
575
* @param ejbFiles Hashtable of EJB classes (and other) files that will be
576
* added to the completed JAR file
577
* @param jarFile JAR file which will contain all of the EJB classes (and
579
* @return boolean indicating whether or not the <code>jarFile</code>
582
protected boolean needToRebuild(Hashtable ejbFiles, File jarFile) {
583
if (jarFile.exists()) {
584
long lastBuild = jarFile.lastModified();
585
if (config.manifest != null && config.manifest.exists() &&
586
config.manifest.lastModified() > lastBuild) {
587
log("Build needed because manifest " + config.manifest + " is out of date",
588
Project.MSG_VERBOSE);
593
Iterator fileIter = ejbFiles.values().iterator();
595
// Loop through the files seeing if any has been touched
596
// more recently than the destination jar.
597
while(fileIter.hasNext()) {
598
File currentFile = (File) fileIter.next();
599
if (lastBuild < currentFile.lastModified()) {
600
log("Build needed because " + currentFile.getPath() + " is out of date",
601
Project.MSG_VERBOSE);
612
* Returns the Public ID of the DTD specified in the EJB descriptor. Not
613
* every vendor-specific <code>DeploymentTool</code> will need to reference
614
* this value or may want to determine this value in a vendor-specific way.
616
* @return Public ID of the DTD specified in the EJB descriptor.
618
protected String getPublicId() {
619
return handler.getPublicId();
623
* Method used to encapsulate the writing of the JAR file. Iterates over the
624
* filenames/java.io.Files in the Hashtable stored on the instance variable
627
protected void writeJar(String baseName, File jarfile, Hashtable files,
628
String publicId) throws BuildException{
630
JarOutputStream jarStream = null;
632
// clean the addedfiles Vector
633
addedfiles = new ArrayList();
635
/* If the jarfile already exists then whack it and recreate it.
636
* Should probably think of a more elegant way to handle this
637
* so that in case of errors we don't leave people worse off
638
* than when we started =)
640
if (jarfile.exists()) {
643
jarfile.getParentFile().mkdirs();
644
jarfile.createNewFile();
646
InputStream in = null;
647
Manifest manifest = null;
649
if (config.manifest != null) {
650
in = new FileInputStream(config.manifest);
652
throw new BuildException("Could not find manifest file: " + config.manifest,
657
String defaultManifest = "/org/apache/tools/ant/defaultManifest.mf";
658
in = this.getClass().getResourceAsStream(defaultManifest);
660
throw new BuildException("Could not find default manifest: " + defaultManifest,
665
manifest = new Manifest(in);
667
catch (IOException e) {
668
throw new BuildException ("Unable to read manifest", e, getLocation());
676
// Create the streams necessary to write the jarfile
678
jarStream = new JarOutputStream(new FileOutputStream(jarfile), manifest);
679
jarStream.setMethod(JarOutputStream.DEFLATED);
681
// Loop through all the class files found and add them to the jar
682
for (Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext(); ) {
683
String entryName = (String) entryIterator.next();
684
File entryFile = (File) files.get(entryName);
686
log("adding file '" + entryName + "'",
687
Project.MSG_VERBOSE);
689
addFileToJar(jarStream, entryFile, entryName);
691
// See if there are any inner classes for this class and add them in if there are
692
InnerClassFilenameFilter flt = new InnerClassFilenameFilter(entryFile.getName());
693
File entryDir = entryFile.getParentFile();
694
String[] innerfiles = entryDir.list(flt);
695
for (int i = 0, n = innerfiles.length; i < n; i++) {
697
//get and clean up innerclass name
698
int entryIndex = entryName.lastIndexOf(entryFile.getName()) -1;
699
if ( entryIndex < 0) {
700
entryName = innerfiles[i];
703
entryName = entryName.substring(0, entryIndex) + File.separatorChar + innerfiles[i];
706
entryFile = new File(config.srcDir, entryName);
708
log("adding innerclass file '" + entryName + "'",
709
Project.MSG_VERBOSE);
711
addFileToJar(jarStream, entryFile, entryName);
716
catch(IOException ioe) {
717
String msg = "IOException while processing ejb-jar file '"
721
throw new BuildException(msg, ioe);
724
if (jarStream != null) {
728
catch (IOException closeException) {}
734
* Check if a EJB Class Inherits from a Superclass, and if a Remote Interface
735
* extends an interface other then javax.ejb.EJBObject directly. Then add those
736
* classes to the generic-jar so they dont have to added elsewhere.
739
protected void checkAndAddInherited(Hashtable checkEntries) throws BuildException
741
//Copy hashtable so were not changing the one we iterate through
742
Hashtable copiedHash = (Hashtable)checkEntries.clone();
744
// Walk base level EJBs and see if they have superclasses or extend extra interfaces which extend EJBObject
745
for (Iterator entryIterator = copiedHash.keySet().iterator(); entryIterator.hasNext(); )
747
String entryName = (String)entryIterator.next();
748
File entryFile = (File)copiedHash.get(entryName);
750
// only want class files, xml doesnt reflect very well =)
751
if (entryName.endsWith(".class"))
753
String classname = entryName.substring(0,entryName.lastIndexOf(".class")).replace(File.separatorChar,'.');
754
ClassLoader loader = getClassLoaderForBuild();
756
Class c = loader.loadClass(classname);
758
// No primatives!! sanity check, probably not nessesary
759
if (!c.isPrimitive())
761
if (c.isInterface()) //get as an interface
763
log("looking at interface " + c.getName(), Project.MSG_VERBOSE);
764
Class[] interfaces = c.getInterfaces();
765
for (int i = 0; i < interfaces.length; i++){
766
log(" implements " + interfaces[i].getName(), Project.MSG_VERBOSE);
767
addInterface(interfaces[i], checkEntries);
770
else // get as a class
772
log("looking at class " + c.getName(), Project.MSG_VERBOSE);
773
Class s = c.getSuperclass();
774
addSuperClass(c.getSuperclass(), checkEntries);
778
catch (ClassNotFoundException cnfe) {
779
log("Could not load class " + classname + " for super class check",
782
catch (NoClassDefFoundError ncdfe) {
783
log("Could not fully load class " + classname + " for super class check",
790
private void addInterface(Class theInterface, Hashtable checkEntries) {
791
if (!theInterface.getName().startsWith("java")) // do not add system interfaces
793
File interfaceFile = new File(config.srcDir.getAbsolutePath()
795
+ theInterface.getName().replace('.',File.separatorChar)
798
if (interfaceFile.exists() && interfaceFile.isFile())
800
checkEntries.put(theInterface.getName().replace('.',File.separatorChar)+".class",
802
Class[] superInterfaces = theInterface.getInterfaces();
803
for (int i = 0; i < superInterfaces.length; i++) {
804
addInterface(superInterfaces[i], checkEntries);
810
private void addSuperClass(Class superClass, Hashtable checkEntries) {
812
if (!superClass.getName().startsWith("java"))
814
File superClassFile = new File(config.srcDir.getAbsolutePath()
816
+ superClass.getName().replace('.',File.separatorChar)
818
if (superClassFile.exists() && superClassFile.isFile())
820
checkEntries.put(superClass.getName().replace('.',File.separatorChar) + ".class",
823
// now need to get super classes and interfaces for this class
824
Class[] superInterfaces = superClass.getInterfaces();
825
for (int i = 0; i < superInterfaces.length; i++) {
826
addInterface(superInterfaces[i], checkEntries);
829
addSuperClass(superClass.getSuperclass(), checkEntries);
835
* Returns a Classloader object which parses the passed in generic EjbJar classpath.
836
* The loader is used to dynamically load classes from javax.ejb.* and the classes
837
* being added to the jar.
840
protected ClassLoader getClassLoaderForBuild()
842
if (classpathLoader != null) {
843
return classpathLoader;
846
Path combinedClasspath = getCombinedClasspath();
848
// only generate a new ClassLoader if we have a classpath
849
if (combinedClasspath == null) {
850
classpathLoader = getClass().getClassLoader();
853
classpathLoader = new AntClassLoader(getTask().getProject(), combinedClasspath);
856
return classpathLoader;
860
* Called to validate that the tool parameters have been configured.
862
* @throws BuildException If the Deployment Tool's configuration isn't
865
public void validateConfigured() throws BuildException {
866
if ((destDir == null) || (!destDir.isDirectory())) {
867
String msg = "A valid destination directory must be specified "
868
+ "using the \"destdir\" attribute.";
869
throw new BuildException(msg, getLocation());