1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.URL;
25 import java.util.Map;
26 import java.util.HashMap;
27 import java.util.Enumeration;
28 import java.util.Locale;
29 import java.util.NoSuchElementException;
30 import java.util.Properties;
31
32 import org.apache.tools.ant.AntTypeDefinition;
33 import org.apache.tools.ant.ComponentHelper;
34 import org.apache.tools.ant.BuildException;
35 import org.apache.tools.ant.Project;
36 import org.apache.tools.ant.ProjectHelper;
37 import org.apache.tools.ant.MagicNames;
38 import org.apache.tools.ant.util.FileUtils;
39 import org.apache.tools.ant.types.EnumeratedAttribute;
40
41 /**
42 * Base class for Taskdef and Typedef - handles all
43 * the attributes for Typedef. The uri and class
44 * handling is handled by DefBase
45 *
46 * @since Ant 1.4
47 */
48 public abstract class Definer extends DefBase {
49
50 /**
51 * the extension of an antlib file for autoloading.
52 * {@value[
53 */
54 private static final String ANTLIB_XML = "/antlib.xml";
55
56 private static class ResourceStack extends ThreadLocal {
57 public Object initialValue() {
58 return new HashMap();
59 }
60 Map getStack() {
61 return (Map) get();
62 }
63 }
64 private static ResourceStack resourceStack = new ResourceStack();
65 private String name;
66 private String classname;
67 private File file;
68 private String resource;
69
70 private int format = Format.PROPERTIES;
71 private boolean definerSet = false;
72 private int onError = OnError.FAIL;
73 private String adapter;
74 private String adaptTo;
75
76 private Class adapterClass;
77 private Class adaptToClass;
78
79 /**
80 * Enumerated type for onError attribute
81 *
82 * @see EnumeratedAttribute
83 */
84 public static class OnError extends EnumeratedAttribute {
85 /** Enumerated values */
86 public static final int FAIL = 0, REPORT = 1, IGNORE = 2, FAIL_ALL = 3;
87
88 /**
89 * text value of onerror option {@value}
90 */
91 public static final String POLICY_FAIL = "fail";
92 /**
93 * text value of onerror option {@value}
94 */
95 public static final String POLICY_REPORT = "report";
96 /**
97 * text value of onerror option {@value}
98 */
99 public static final String POLICY_IGNORE = "ignore";
100 /**
101 * text value of onerror option {@value}
102 */
103 public static final String POLICY_FAILALL = "failall";
104
105 /**
106 * Constructor
107 */
108 public OnError() {
109 super();
110 }
111
112 /**
113 * Constructor using a string.
114 * @param value the value of the attribute
115 */
116 public OnError(String value) {
117 setValue(value);
118 }
119
120 /**
121 * get the values
122 * @return an array of the allowed values for this attribute.
123 */
124 public String[] getValues() {
125 return new String[] {POLICY_FAIL, POLICY_REPORT, POLICY_IGNORE, POLICY_FAILALL};
126 }
127 }
128
129 /**
130 * Enumerated type for format attribute
131 *
132 * @see EnumeratedAttribute
133 */
134 public static class Format extends EnumeratedAttribute {
135 /** Enumerated values */
136 public static final int PROPERTIES = 0, XML = 1;
137
138 /**
139 * get the values
140 * @return an array of the allowed values for this attribute.
141 */
142 public String[] getValues() {
143 return new String[] {"properties", "xml"};
144 }
145 }
146
147 /**
148 * What to do if there is an error in loading the class.
149 * <dl>
150 * <li>error - throw build exception</li>
151 * <li>report - output at warning level</li>
152 * <li>ignore - output at debug level</li>
153 * </dl>
154 *
155 * @param onError an <code>OnError</code> value
156 */
157 public void setOnError(OnError onError) {
158 this.onError = onError.getIndex();
159 }
160
161 /**
162 * Sets the format of the file or resource
163 * @param format the enumerated value - xml or properties
164 */
165 public void setFormat(Format format) {
166 this.format = format.getIndex();
167 }
168
169 /**
170 * @return the name for this definition
171 */
172 public String getName() {
173 return name;
174 }
175
176 /**
177 * @return the file containing definitions
178 */
179 public File getFile() {
180 return file;
181 }
182
183 /**
184 * @return the resource containing definitions
185 */
186 public String getResource() {
187 return resource;
188 }
189
190
191 /**
192 * Run the definition.
193 *
194 * @exception BuildException if an error occurs
195 */
196 public void execute() throws BuildException {
197 ClassLoader al = createLoader();
198
199 if (!definerSet) {
200 //we arent fully defined yet. this is an error unless
201 //we are in an antlib, in which case the resource name is determined
202 //automatically.
203 //NB: URIs in the ant core package will be "" at this point.
204 if (getURI() == null) {
205 throw new BuildException(
206 "name, file or resource attribute of "
207 + getTaskName() + " is undefined",
208 getLocation());
209 }
210
211 if (getURI().startsWith(MagicNames.ANTLIB_PREFIX)) {
212 //convert the URI to a resource
213 String uri1 = getURI();
214 setResource(makeResourceFromURI(uri1));
215 } else {
216 throw new BuildException(
217 "Only antlib URIs can be located from the URI alone,"
218 + "not the URI " + getURI());
219 }
220 }
221
222 if (name != null) {
223 if (classname == null) {
224 throw new BuildException(
225 "classname attribute of " + getTaskName() + " element "
226 + "is undefined", getLocation());
227 }
228 addDefinition(al, name, classname);
229 } else {
230 if (classname != null) {
231 String msg = "You must not specify classname "
232 + "together with file or resource.";
233 throw new BuildException(msg, getLocation());
234 }
235 Enumeration/*<URL>*/ urls = null;
236 if (file != null) {
237 final URL url = fileToURL();
238 if (url == null) {
239 return;
240 }
241 urls = new Enumeration() {
242 private boolean more = true;
243 public boolean hasMoreElements() {
244 return more;
245 }
246 public Object nextElement() throws NoSuchElementException {
247 if (more) {
248 more = false;
249 return url;
250 } else {
251 throw new NoSuchElementException();
252 }
253 }
254 };
255 } else {
256 urls = resourceToURLs(al);
257 }
258
259 while (urls.hasMoreElements()) {
260 URL url = (URL) urls.nextElement();
261
262 int fmt = this.format;
263 if (url.toString().toLowerCase(Locale.US).endsWith(".xml")) {
264 fmt = Format.XML;
265 }
266
267 if (fmt == Format.PROPERTIES) {
268 loadProperties(al, url);
269 break;
270 } else {
271 if (resourceStack.getStack().get(url) != null) {
272 log("Warning: Recursive loading of " + url
273 + " ignored"
274 + " at " + getLocation()
275 + " originally loaded at "
276 + resourceStack.getStack().get(url),
277 Project.MSG_WARN);
278 } else {
279 try {
280 resourceStack.getStack().put(url, getLocation());
281 loadAntlib(al, url);
282 } finally {
283 resourceStack.getStack().remove(url);
284 }
285 }
286 }
287 }
288 }
289 }
290
291 /**
292 * This is where the logic to map from a URI to an antlib resource
293 * is kept.
294 * @param uri the xml namespace uri that to convert.
295 * @return the name of a resource. It may not exist
296 */
297
298 public static String makeResourceFromURI(String uri) {
299 String path = uri.substring(MagicNames.ANTLIB_PREFIX.length());
300 String resource;
301 if (path.startsWith("//")) {
302 //handle new style full paths to an antlib, in which
303 //all but the forward slashes are allowed.
304 resource = path.substring("//".length());
305 if (!resource.endsWith(".xml")) {
306 //if we haven't already named an XML file, it gets antlib.xml
307 resource = resource + ANTLIB_XML;
308 }
309 } else {
310 //convert from a package to a path
311 resource = path.replace('.', '/') + ANTLIB_XML;
312 }
313 return resource;
314 }
315
316 /**
317 * Convert a file to a file: URL.
318 *
319 * @return the URL, or null if it isn't valid and the active error policy
320 * is not to raise a fault
321 * @throws BuildException if the file is missing/not a file and the
322 * policy requires failure at this point.
323 */
324 private URL fileToURL() {
325 String message = null;
326 if (!(file.exists())) {
327 message = "File " + file + " does not exist";
328 }
329 if (message == null && !(file.isFile())) {
330 message = "File " + file + " is not a file";
331 }
332 try {
333 if (message == null) {
334 return file.toURL();
335 }
336 } catch (Exception ex) {
337 message =
338 "File " + file + " cannot use as URL: "
339 + ex.toString();
340 }
341 // Here if there is an error
342 switch (onError) {
343 case OnError.FAIL_ALL:
344 throw new BuildException(message);
345 case OnError.FAIL:
346 // Fall Through
347 case OnError.REPORT:
348 log(message, Project.MSG_WARN);
349 break;
350 case OnError.IGNORE:
351 // log at a lower level
352 log(message, Project.MSG_VERBOSE);
353 break;
354 default:
355 // Ignore the problem
356 break;
357 }
358 return null;
359 }
360
361 private Enumeration/*<URL>*/ resourceToURLs(ClassLoader classLoader) {
362 Enumeration ret;
363 try {
364 ret = classLoader.getResources(resource);
365 } catch (IOException e) {
366 throw new BuildException(
367 "Could not fetch resources named " + resource,
368 e, getLocation());
369 }
370 if (!ret.hasMoreElements()) {
371 String message = "Could not load definitions from resource "
372 + resource + ". It could not be found.";
373 switch (onError) {
374 case OnError.FAIL_ALL:
375 throw new BuildException(message);
376 case OnError.FAIL:
377 case OnError.REPORT:
378 log(message, Project.MSG_WARN);
379 break;
380 case OnError.IGNORE:
381 log(message, Project.MSG_VERBOSE);
382 break;
383 default:
384 // Ignore the problem
385 break;
386 }
387 }
388 return ret;
389 }
390
391 /**
392 * Load type definitions as properties from a URL.
393 *
394 * @param al the classloader to use
395 * @param url the url to get the definitions from
396 */
397 protected void loadProperties(ClassLoader al, URL url) {
398 InputStream is = null;
399 try {
400 is = url.openStream();
401 if (is == null) {
402 log("Could not load definitions from " + url,
403 Project.MSG_WARN);
404 return;
405 }
406 Properties props = new Properties();
407 props.load(is);
408 Enumeration keys = props.keys();
409 while (keys.hasMoreElements()) {
410 name = ((String) keys.nextElement());
411 classname = props.getProperty(name);
412 addDefinition(al, name, classname);
413 }
414 } catch (IOException ex) {
415 throw new BuildException(ex, getLocation());
416 } finally {
417 FileUtils.close(is);
418 }
419 }
420
421 /**
422 * Load an antlib from a URL.
423 *
424 * @param classLoader the classloader to use.
425 * @param url the url to load the definitions from.
426 */
427 private void loadAntlib(ClassLoader classLoader, URL url) {
428 try {
429 Antlib antlib = Antlib.createAntlib(getProject(), url, getURI());
430 antlib.setClassLoader(classLoader);
431 antlib.setURI(getURI());
432 antlib.execute();
433 } catch (BuildException ex) {
434 throw ProjectHelper.addLocationToBuildException(
435 ex, getLocation());
436 }
437 }
438
439 /**
440 * Name of the property file to load
441 * ant name/classname pairs from.
442 * @param file the file
443 */
444 public void setFile(File file) {
445 if (definerSet) {
446 tooManyDefinitions();
447 }
448 definerSet = true;
449 this.file = file;
450 }
451
452 /**
453 * Name of the property resource to load
454 * ant name/classname pairs from.
455 * @param res the resource to use
456 */
457 public void setResource(String res) {
458 if (definerSet) {
459 tooManyDefinitions();
460 }
461 definerSet = true;
462 this.resource = res;
463 }
464
465 /**
466 * Antlib attribute, sets resource and uri.
467 * uri is set the antlib value and, resource is set
468 * to the antlib.xml resource in the classpath.
469 * For example antlib="antlib:org.acme.bland.cola"
470 * corresponds to uri="antlib:org.acme.bland.cola"
471 * resource="org/acme/bland/cola/antlib.xml".
472 * ASF Bugzilla Bug 31999
473 * @param antlib the value to set.
474 */
475 public void setAntlib(String antlib) {
476 if (definerSet) {
477 tooManyDefinitions();
478 }
479 if (!antlib.startsWith("antlib:")) {
480 throw new BuildException(
481 "Invalid antlib attribute - it must start with antlib:");
482 }
483 setURI(antlib);
484 this.resource = antlib.substring("antlib:".length()).replace('.', '/')
485 + "/antlib.xml";
486 definerSet = true;
487 }
488
489 /**
490 * Name of the definition
491 * @param name the name of the definition
492 */
493 public void setName(String name) {
494 if (definerSet) {
495 tooManyDefinitions();
496 }
497 definerSet = true;
498 this.name = name;
499 }
500
501 /**
502 * Returns the classname of the object we are defining.
503 * May be <code>null</code>.
504 * @return the class name
505 */
506 public String getClassname() {
507 return classname;
508 }
509
510 /**
511 * The full class name of the object being defined.
512 * Required, unless file or resource have
513 * been specified.
514 * @param classname the name of the class
515 */
516 public void setClassname(String classname) {
517 this.classname = classname;
518 }
519
520 /**
521 * Set the class name of the adapter class.
522 * An adapter class is used to proxy the
523 * definition class. It is used if the
524 * definition class is not assignable to
525 * the adaptto class, or if the adaptto
526 * class is not present.
527 *
528 * @param adapter the name of the adapter class
529 */
530
531 public void setAdapter(String adapter) {
532 this.adapter = adapter;
533 }
534
535 /**
536 * Set the adapter class.
537 *
538 * @param adapterClass the class to use to adapt the definition class
539 */
540 protected void setAdapterClass(Class adapterClass) {
541 this.adapterClass = adapterClass;
542 }
543
544 /**
545 * Set the classname of the class that the definition
546 * must be compatible with, either directly or
547 * by use of the adapter class.
548 *
549 * @param adaptTo the name of the adaptto class
550 */
551 public void setAdaptTo(String adaptTo) {
552 this.adaptTo = adaptTo;
553 }
554
555 /**
556 * Set the class for adaptToClass, to be
557 * used by derived classes, used instead of
558 * the adaptTo attribute.
559 *
560 * @param adaptToClass the class for adapto.
561 */
562 protected void setAdaptToClass(Class adaptToClass) {
563 this.adaptToClass = adaptToClass;
564 }
565
566
567 /**
568 * Add a definition using the attributes of Definer
569 *
570 * @param al the ClassLoader to use
571 * @param name the name of the definition
572 * @param classname the classname of the definition
573 * @exception BuildException if an error occurs
574 */
575 protected void addDefinition(ClassLoader al, String name, String classname)
576 throws BuildException {
577 Class cl = null;
578 try {
579 try {
580 name = ProjectHelper.genComponentName(getURI(), name);
581
582 if (onError != OnError.IGNORE) {
583 cl = Class.forName(classname, true, al);
584 }
585
586 if (adapter != null) {
587 adapterClass = Class.forName(adapter, true, al);
588 }
589
590 if (adaptTo != null) {
591 adaptToClass = Class.forName(adaptTo, true, al);
592 }
593
594 AntTypeDefinition def = new AntTypeDefinition();
595 def.setName(name);
596 def.setClassName(classname);
597 def.setClass(cl);
598 def.setAdapterClass(adapterClass);
599 def.setAdaptToClass(adaptToClass);
600 def.setClassLoader(al);
601 if (cl != null) {
602 def.checkClass(getProject());
603 }
604 ComponentHelper.getComponentHelper(getProject())
605 .addDataTypeDefinition(def);
606 } catch (ClassNotFoundException cnfe) {
607 String msg = getTaskName() + " class " + classname
608 + " cannot be found";
609 throw new BuildException(msg, cnfe, getLocation());
610 } catch (NoClassDefFoundError ncdfe) {
611 String msg = getTaskName() + " A class needed by class "
612 + classname + " cannot be found: " + ncdfe.getMessage();
613 throw new BuildException(msg, ncdfe, getLocation());
614 }
615 } catch (BuildException ex) {
616 switch (onError) {
617 case OnError.FAIL_ALL:
618 case OnError.FAIL:
619 throw ex;
620 case OnError.REPORT:
621 log(ex.getLocation() + "Warning: " + ex.getMessage(),
622 Project.MSG_WARN);
623 break;
624 default:
625 log(ex.getLocation() + ex.getMessage(),
626 Project.MSG_DEBUG);
627 }
628 }
629 }
630
631 /**
632 * handle too many definitions by raising an exception.
633 * @throws BuildException always.
634 */
635 private void tooManyDefinitions() {
636 throw new BuildException(
637 "Only one of the attributes name, file and resource"
638 + " can be set", getLocation());
639 }
640 }