source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/types/XMLCatalog.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 40.9 KB
Line 
1/*
2 * Copyright 2002-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.apache.tools.ant.types;
19
20import java.lang.reflect.Method;
21
22import java.io.File;
23import java.io.FileInputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.net.MalformedURLException;
27import java.net.URL;
28import java.util.Enumeration;
29import java.util.Stack;
30import java.util.Vector;
31import javax.xml.parsers.ParserConfigurationException;
32import javax.xml.parsers.SAXParserFactory;
33import javax.xml.transform.Source;
34import javax.xml.transform.TransformerException;
35import javax.xml.transform.URIResolver;
36import javax.xml.transform.sax.SAXSource;
37import org.apache.tools.ant.AntClassLoader;
38import org.apache.tools.ant.BuildException;
39import org.apache.tools.ant.Project;
40import org.apache.tools.ant.util.FileUtils;
41import org.apache.tools.ant.util.JAXPUtils;
42import org.xml.sax.EntityResolver;
43import org.xml.sax.InputSource;
44import org.xml.sax.SAXException;
45import org.xml.sax.XMLReader;
46
47
48
49/**
50 * <p>This data type provides a catalog of resource locations (such as
51 * DTDs and XML entities), based on the <a
52 * href="http://oasis-open.org/committees/entity/spec-2001-08-06.html">
53 * OASIS "Open Catalog" standard</a>. The catalog entries are used
54 * both for Entity resolution and URI resolution, in accordance with
55 * the {@link org.xml.sax.EntityResolver EntityResolver} and {@link
56 * javax.xml.transform.URIResolver URIResolver} interfaces as defined
57 * in the <a href="http://java.sun.com/xml/jaxp">Java API for XML
58 * Processing Specification</a>.</p>
59 *
60 * <p>Resource locations can be specified either in-line or in
61 * external catalog file(s), or both. In order to use an external
62 * catalog file, the xml-commons resolver library ("resolver.jar")
63 * must be in your classpath. External catalog files may be either <a
64 * href="http://oasis-open.org/committees/entity/background/9401.html">
65 * plain text format</a> or <a
66 * href="http://www.oasis-open.org/committees/entity/spec-2001-08-06.html">
67 * XML format</a>. If the xml-commons resolver library is not found
68 * in the classpath, external catalog files, specified in
69 * <code>&lt;catalogpath&gt;</code> paths, will be ignored and a warning will
70 * be logged. In this case, however, processing of inline entries will proceed
71 * normally.</p>
72 *
73 * <p>Currently, only <code>&lt;dtd&gt;</code> and
74 * <code>&lt;entity&gt;</code> elements may be specified inline; these
75 * correspond to OASIS catalog entry types <code>PUBLIC</code> and
76 * <code>URI</code> respectively.</p>
77 *
78 * <p>The following is a usage example:</p>
79 *
80 * <code>
81 * &lt;xmlcatalog&gt;<br>
82 * &nbsp;&nbsp;&lt;dtd publicId="" location="/path/to/file.jar" /&gt;<br>
83 * &nbsp;&nbsp;&lt;dtd publicId="" location="/path/to/file2.jar" /&gt;<br>
84 * &nbsp;&nbsp;&lt;entity publicId="" location="/path/to/file3.jar" /&gt;<br>
85 * &nbsp;&nbsp;&lt;entity publicId="" location="/path/to/file4.jar" /&gt;<br>
86 * &nbsp;&nbsp;&lt;catalogpath&gt;<br>
87 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;pathelement location="/etc/sgml/catalog"/&gt;<br>
88 * &nbsp;&nbsp;&lt;/catalogpath&gt;<br>
89 * &nbsp;&nbsp;&lt;catalogfiles dir="/opt/catalogs/" includes="**\catalog.xml" /&gt;<br>
90 * &lt;/xmlcatalog&gt;<br>
91 * </code>
92 * <p>
93 * Tasks wishing to use <code>&lt;xmlcatalog&gt;</code> must provide a method called
94 * <code>createXMLCatalog</code> which returns an instance of
95 * <code>XMLCatalog</code>. Nested DTD and entity definitions are handled by
96 * the XMLCatalog object and must be labeled <code>dtd</code> and
97 * <code>entity</code> respectively.</p>
98 *
99 * <p>The following is a description of the resolution algorithm:
100 * entities/URIs/dtds are looked up in each of the following contexts,
101 * stopping when a valid and readable resource is found:
102 * <ol>
103 * <li>In the local filesystem</li>
104 * <li>In the classpath</li>
105 * <li>Using the Apache xml-commons resolver (if it is available)</li>
106 * <li>In URL-space</li>
107 * </ol>
108 * </p>
109 *
110 * <p>See {@link
111 * org.apache.tools.ant.taskdefs.optional.XMLValidateTask
112 * XMLValidateTask} for an example of a task that has integrated
113 * support for XMLCatalogs.</p>
114 *
115 * <p>Possible future extension could provide for additional OASIS
116 * entry types to be specified inline.</p>
117 *
118 */
119public class XMLCatalog extends DataType
120 implements Cloneable, EntityResolver, URIResolver {
121
122 /** helper for some File.toURL connversions */
123 private static FileUtils fileUtils = FileUtils.newFileUtils();
124
125 //-- Fields ----------------------------------------------------------------
126
127 /** Holds dtd/entity objects until needed. */
128 private Vector elements = new Vector();
129
130 /**
131 * Classpath in which to attempt to resolve resources.
132 */
133 private Path classpath;
134
135 /**
136 * Path listing external catalog files to search when resolving entities
137 */
138 private Path catalogPath;
139
140 /**
141 * The name of the bridge to the Apache xml-commons resolver
142 * class, used to determine whether resolver.jar is present in the
143 * classpath.
144 */
145 public static final String APACHE_RESOLVER
146 = "org.apache.tools.ant.types.resolver.ApacheCatalogResolver";
147
148 /**
149 * Resolver base class
150 */
151 public static final String CATALOG_RESOLVER
152 = "org.apache.xml.resolver.tools.CatalogResolver";
153
154 //-- Methods ---------------------------------------------------------------
155
156 /**
157 * Default constructor
158 */
159 public XMLCatalog() {
160 setChecked(false);
161 }
162
163 /**
164 * Returns the elements of the catalog - ResourceLocation objects.
165 *
166 * @return the elements of the catalog - ResourceLocation objects
167 */
168 private Vector getElements() {
169 return getRef().elements;
170 }
171
172 /**
173 * Returns the classpath in which to attempt to resolve resources.
174 *
175 * @return the classpath
176 */
177 private Path getClasspath() {
178 return getRef().classpath;
179 }
180
181 /**
182 * Set the list of ResourceLocation objects in the catalog.
183 * Not allowed if this catalog is itself a reference to another catalog --
184 * that is, a catalog cannot both refer to another <em>and</em> contain
185 * elements or other attributes.
186 *
187 * @param aVector the new list of ResourceLocations
188 * to use in the catalog.
189 */
190 private void setElements(Vector aVector) {
191 if (isReference()) {
192 throw noChildrenAllowed();
193 }
194 elements = aVector;
195 }
196
197 /**
198 * Allows nested classpath elements. Not allowed if this catalog
199 * is itself a reference to another catalog -- that is, a catalog
200 * cannot both refer to another <em>and</em> contain elements or
201 * other attributes.
202 *
203 * @return a Path instance to be configured.
204 */
205 public Path createClasspath() {
206 if (isReference()) {
207 throw noChildrenAllowed();
208 }
209 if (this.classpath == null) {
210 this.classpath = new Path(getProject());
211 }
212 setChecked(false);
213 return this.classpath.createPath();
214 }
215
216 /**
217 * Allows simple classpath string. Not allowed if this catalog is
218 * itself a reference to another catalog -- that is, a catalog
219 * cannot both refer to another <em>and</em> contain elements or
220 * other attributes.
221 *
222 * @param classpath the classpath to use to look up entities.
223 */
224 public void setClasspath(Path classpath) {
225 if (isReference()) {
226 throw tooManyAttributes();
227 }
228 if (this.classpath == null) {
229 this.classpath = classpath;
230 } else {
231 this.classpath.append(classpath);
232 }
233 setChecked(false);
234 }
235
236 /**
237 * Allows classpath reference. Not allowed if this catalog is
238 * itself a reference to another catalog -- that is, a catalog
239 * cannot both refer to another <em>and</em> contain elements or
240 * other attributes.
241 *
242 * @param r an Ant reference containing a classpath.
243 */
244 public void setClasspathRef(Reference r) {
245 if (isReference()) {
246 throw tooManyAttributes();
247 }
248 createClasspath().setRefid(r);
249 setChecked(false);
250 }
251
252 /** Creates a nested <code>&lt;catalogpath&gt;</code> element.
253 * Not allowed if this catalog is itself a reference to another
254 * catalog -- that is, a catalog cannot both refer to another
255 * <em>and</em> contain elements or other attributes.
256 *
257 * @return a path to be configured as the catalog path.
258 * @exception BuildException
259 * if this is a reference and no nested elements are allowed.
260 */
261 public Path createCatalogPath() {
262 if (isReference()) {
263 throw noChildrenAllowed();
264 }
265 if (this.catalogPath == null) {
266 this.catalogPath = new Path(getProject());
267 }
268 setChecked(false);
269 return this.catalogPath.createPath();
270 }
271
272 /**
273 * Allows catalogpath reference. Not allowed if this catalog is
274 * itself a reference to another catalog -- that is, a catalog
275 * cannot both refer to another <em>and</em> contain elements or
276 * other attributes.
277 *
278 * @param r an Ant reference containing a classpath to be used as
279 * the catalog path.
280 */
281 public void setCatalogPathRef(Reference r) {
282 if (isReference()) {
283 throw tooManyAttributes();
284 }
285 createCatalogPath().setRefid(r);
286 setChecked(false);
287 }
288
289
290 /**
291 * Returns the catalog path in which to attempt to resolve DTDs.
292 *
293 * @return the catalog path
294 */
295 public Path getCatalogPath() {
296 return getRef().catalogPath;
297 }
298
299
300 /**
301 * Creates the nested <code>&lt;dtd&gt;</code> element. Not
302 * allowed if this catalog is itself a reference to another
303 * catalog -- that is, a catalog cannot both refer to another
304 * <em>and</em> contain elements or other attributes.
305 *
306 * @param dtd the information about the PUBLIC resource mapping to
307 * be added to the catalog
308 * @exception BuildException if this is a reference and no nested
309 * elements are allowed.
310 */
311 public void addDTD(ResourceLocation dtd) throws BuildException {
312 if (isReference()) {
313 throw noChildrenAllowed();
314 }
315
316 getElements().addElement(dtd);
317 setChecked(false);
318 }
319
320 /**
321 * Creates the nested <code>&lt;entity&gt;</code> element. Not
322 * allowed if this catalog is itself a reference to another
323 * catalog -- that is, a catalog cannot both refer to another
324 * <em>and</em> contain elements or other attributes.
325 *
326 * @param entity the information about the URI resource mapping to be
327 * added to the catalog.
328 * @exception BuildException if this is a reference and no nested
329 * elements are allowed.
330 */
331 public void addEntity(ResourceLocation entity) throws BuildException {
332 addDTD(entity);
333 }
334
335 /**
336 * Loads a nested <code>&lt;xmlcatalog&gt;</code> into our
337 * definition. Not allowed if this catalog is itself a reference
338 * to another catalog -- that is, a catalog cannot both refer to
339 * another <em>and</em> contain elements or other attributes.
340 *
341 * @param catalog Nested XMLCatalog
342 */
343 public void addConfiguredXMLCatalog(XMLCatalog catalog) {
344 if (isReference()) {
345 throw noChildrenAllowed();
346 }
347
348 // Add all nested elements to our catalog
349 Vector newElements = catalog.getElements();
350 Vector ourElements = getElements();
351 Enumeration e = newElements.elements();
352 while (e.hasMoreElements()) {
353 ourElements.addElement(e.nextElement());
354 }
355
356 // Append the classpath of the nested catalog
357 Path nestedClasspath = catalog.getClasspath();
358 createClasspath().append(nestedClasspath);
359
360 // Append the catalog path of the nested catalog
361 Path nestedCatalogPath = catalog.getCatalogPath();
362 createCatalogPath().append(nestedCatalogPath);
363 setChecked(false);
364 }
365
366 /**
367 * Makes this instance in effect a reference to another XMLCatalog
368 * instance.
369 *
370 * <p>You must not set another attribute or nest elements inside
371 * this element if you make it a reference. That is, a catalog
372 * cannot both refer to another <em>and</em> contain elements or
373 * attributes.</p>
374 *
375 * @param r the reference to which this catalog instance is associated
376 * @exception BuildException if this instance already has been configured.
377 */
378 public void setRefid(Reference r) throws BuildException {
379 if (!elements.isEmpty()) {
380 throw tooManyAttributes();
381 }
382 super.setRefid(r);
383 }
384
385 /**
386 * Implements the EntityResolver.resolveEntity() interface method.
387 *
388 * @see org.xml.sax.EntityResolver#resolveEntity
389 */
390 public InputSource resolveEntity(String publicId, String systemId)
391 throws SAXException, IOException {
392
393 if (isReference()) {
394 return getRef().resolveEntity(publicId, systemId);
395 }
396
397 if (!isChecked()) {
398 // make sure we don't have a circular reference here
399 Stack stk = new Stack();
400 stk.push(this);
401 dieOnCircularReference(stk, getProject());
402 }
403
404 log("resolveEntity: '" + publicId + "': '" + systemId + "'",
405 Project.MSG_DEBUG);
406
407 InputSource inputSource =
408 getCatalogResolver().resolveEntity(publicId, systemId);
409
410 if (inputSource == null) {
411 log("No matching catalog entry found, parser will use: '"
412 + systemId + "'", Project.MSG_DEBUG);
413 }
414
415 return inputSource;
416 }
417
418 /**
419 * Implements the URIResolver.resolve() interface method.
420 *
421 * @see javax.xml.transform.URIResolver#resolve
422 */
423 public Source resolve(String href, String base)
424 throws TransformerException {
425
426 if (isReference()) {
427 return getRef().resolve(href, base);
428 }
429
430 if (!isChecked()) {
431 // make sure we don't have a circular reference here
432 Stack stk = new Stack();
433 stk.push(this);
434 dieOnCircularReference(stk, getProject());
435 }
436
437 SAXSource source = null;
438
439 String uri = removeFragment(href);
440
441 log("resolve: '" + uri + "' with base: '" + base + "'", Project.MSG_DEBUG);
442
443 source = (SAXSource) getCatalogResolver().resolve(uri, base);
444
445 if (source == null) {
446 log("No matching catalog entry found, parser will use: '"
447 + href + "'", Project.MSG_DEBUG);
448 //
449 // Cannot return a null source, because we have to call
450 // setEntityResolver (see setEntityResolver javadoc comment)
451 //
452 source = new SAXSource();
453 URL baseURL = null;
454 try {
455 if (base == null) {
456 baseURL = fileUtils.getFileURL(getProject().getBaseDir());
457 } else {
458 baseURL = new URL(base);
459 }
460 URL url = (uri.length() == 0 ? baseURL : new URL(baseURL, uri));
461 source.setInputSource(new InputSource(url.toString()));
462 } catch (MalformedURLException ex) {
463 // At this point we are probably in failure mode, but
464 // try to use the bare URI as a last gasp
465 source.setInputSource(new InputSource(uri));
466 }
467 }
468
469 setEntityResolver(source);
470 return source;
471 }
472
473 /**
474 * @since Ant 1.6
475 */
476 private XMLCatalog getRef() {
477 if (!isReference()) {
478 return this;
479 }
480 return (XMLCatalog) getCheckedRef(XMLCatalog.class, "xmlcatalog");
481 }
482
483 /**
484 * The instance of the CatalogResolver strategy to use.
485 */
486 private CatalogResolver catalogResolver = null;
487
488 /**
489 * Factory method for creating the appropriate CatalogResolver
490 * strategy implementation.
491 * <p> Until we query the classpath, we don't know whether the Apache
492 * resolver (Norm Walsh's library from xml-commons) is available or not.
493 * This method determines whether the library is available and creates the
494 * appropriate implementation of CatalogResolver based on the answer.</p>
495 * <p>This is an application of the Gang of Four Strategy Pattern
496 * combined with Template Method.</p>
497 */
498 private CatalogResolver getCatalogResolver() {
499
500 if (catalogResolver == null) {
501
502 AntClassLoader loader = null;
503
504 loader = getProject().createClassLoader(Path.systemClasspath);
505
506 try {
507 Class clazz = Class.forName(APACHE_RESOLVER, true, loader);
508
509 // The Apache resolver is present - Need to check if it can
510 // be seen by the catalog resolver class. Start by getting
511 // the actual loader
512 ClassLoader apacheResolverLoader = clazz.getClassLoader();
513
514 // load the base class through this loader.
515 Class baseResolverClass
516 = Class.forName(CATALOG_RESOLVER, true, apacheResolverLoader);
517
518 // and find its actual loader
519 ClassLoader baseResolverLoader
520 = baseResolverClass.getClassLoader();
521
522 // We have the loader which is being used to load the
523 // CatalogResolver. Can it see the ApacheResolver? The
524 // base resolver will only be able to create the ApacheResolver
525 // if it can see it - doesn't use the context loader.
526 clazz = Class.forName(APACHE_RESOLVER, true, baseResolverLoader);
527
528 Object obj = clazz.newInstance();
529 //
530 // Success! The xml-commons resolver library is
531 // available, so use it.
532 //
533 catalogResolver = new ExternalResolver(clazz, obj);
534 } catch (Throwable ex) {
535 //
536 // The xml-commons resolver library is not
537 // available, so we can't use it.
538 //
539 catalogResolver = new InternalResolver();
540 if (getCatalogPath() != null
541 && getCatalogPath().list().length != 0) {
542 log("Warning: catalogpath listing external catalogs"
543 + " will be ignored", Project.MSG_WARN);
544 }
545 log("Failed to load Apache resolver: " + ex, Project.MSG_DEBUG);
546 }
547 }
548 return catalogResolver;
549 }
550
551 /**
552 * <p>This is called from the URIResolver to set an EntityResolver
553 * on the SAX parser to be used for new XML documents that are
554 * encountered as a result of the document() function, xsl:import,
555 * or xsl:include. This is done because the XSLT processor calls
556 * out to the SAXParserFactory itself to create a new SAXParser to
557 * parse the new document. The new parser does not automatically
558 * inherit the EntityResolver of the original (although arguably
559 * it should). See below:</p>
560 *
561 * <tt>"If an application wants to set the ErrorHandler or
562 * EntityResolver for an XMLReader used during a transformation,
563 * it should use a URIResolver to return the SAXSource which
564 * provides (with getXMLReader) a reference to the XMLReader"</tt>
565 *
566 * <p>...quoted from page 118 of the Java API for XML
567 * Processing 1.1 specification</p>
568 *
569 */
570 private void setEntityResolver(SAXSource source) throws TransformerException {
571
572 XMLReader reader = source.getXMLReader();
573 if (reader == null) {
574 SAXParserFactory spFactory = SAXParserFactory.newInstance();
575 spFactory.setNamespaceAware(true);
576 try {
577 reader = spFactory.newSAXParser().getXMLReader();
578 } catch (ParserConfigurationException ex) {
579 throw new TransformerException(ex);
580 } catch (SAXException ex) {
581 throw new TransformerException(ex);
582 }
583 }
584 reader.setEntityResolver(this);
585 source.setXMLReader(reader);
586 }
587
588 /**
589 * Find a ResourceLocation instance for the given publicId.
590 *
591 * @param publicId the publicId of the Resource for which local information
592 * is required.
593 * @return a ResourceLocation instance with information on the local location
594 * of the Resource or null if no such information is available.
595 */
596 private ResourceLocation findMatchingEntry(String publicId) {
597 Enumeration e = getElements().elements();
598 ResourceLocation element = null;
599 while (e.hasMoreElements()) {
600 Object o = e.nextElement();
601 if (o instanceof ResourceLocation) {
602 element = (ResourceLocation) o;
603 if (element.getPublicId().equals(publicId)) {
604 return element;
605 }
606 }
607 }
608 return null;
609 }
610
611 /**
612 * Utility method to remove trailing fragment from a URI.
613 * For example,
614 * <code>http://java.sun.com/index.html#chapter1</code>
615 * would return <code>http://java.sun.com/index.html</code>.
616 *
617 * @param uri The URI to process. It may or may not contain a
618 * fragment.
619 * @return The URI sans fragment.
620 */
621 private String removeFragment(String uri) {
622 String result = uri;
623 int hashPos = uri.indexOf("#");
624 if (hashPos >= 0) {
625 result = uri.substring(0, hashPos);
626 }
627 return result;
628 }
629
630 /**
631 * Utility method to lookup a ResourceLocation in the filesystem.
632 *
633 * @return An InputSource for reading the file, or <code>null</code>
634 * if the file does not exist or is not readable.
635 */
636 private InputSource filesystemLookup(ResourceLocation matchingEntry) {
637
638 String uri = matchingEntry.getLocation();
639 // the following line seems to be necessary on Windows under JDK 1.2
640 uri = uri.replace(File.separatorChar, '/');
641 URL baseURL = null;
642
643 //
644 // The ResourceLocation may specify a relative path for its
645 // location attribute. This is resolved using the appropriate
646 // base.
647 //
648 if (matchingEntry.getBase() != null) {
649 baseURL = matchingEntry.getBase();
650 } else {
651 try {
652 baseURL = fileUtils.getFileURL(getProject().getBaseDir());
653 } catch (MalformedURLException ex) {
654 throw new BuildException("Project basedir cannot be converted to a URL");
655 }
656 }
657
658 InputSource source = null;
659 URL url = null;
660 try {
661 url = new URL(baseURL, uri);
662 } catch (MalformedURLException ex) {
663 // this processing is useful under Windows when the location of the DTD has been given as an absolute path
664 // see Bugzilla Report 23913
665 File testFile = new File(uri);
666 if (testFile.exists() && testFile.canRead()) {
667 log("uri : '"
668 + uri + "' matches a readable file", Project.MSG_DEBUG);
669 try {
670 url = fileUtils.getFileURL(testFile);
671 } catch (MalformedURLException ex1) {
672 throw new BuildException("could not find an URL for :" + testFile.getAbsolutePath());
673 }
674 } else {
675 log("uri : '"
676 + uri + "' does not match a readable file", Project.MSG_DEBUG);
677
678 }
679 }
680
681 if (url != null) {
682 String fileName = fileUtils.fromURI(url.toString());
683 if (fileName != null) {
684 log("fileName " + fileName, Project.MSG_DEBUG);
685 File resFile = new File(fileName);
686 if (resFile.exists() && resFile.canRead()) {
687 try {
688 source = new InputSource(new FileInputStream(resFile));
689 String sysid = JAXPUtils.getSystemId(resFile);
690 source.setSystemId(sysid);
691 log("catalog entry matched a readable file: '"
692 + sysid + "'", Project.MSG_DEBUG);
693 } catch (IOException ex) {
694 // ignore
695 }
696 }
697 }
698 }
699 return source;
700 }
701
702 /**
703 * Utility method to lookup a ResourceLocation in the classpath.
704 *
705 * @return An InputSource for reading the resource, or <code>null</code>
706 * if the resource does not exist in the classpath or is not readable.
707 */
708 private InputSource classpathLookup(ResourceLocation matchingEntry) {
709
710 InputSource source = null;
711
712 AntClassLoader loader = null;
713 Path cp = classpath;
714 if (cp != null) {
715 cp = classpath.concatSystemClasspath("ignore");
716 } else {
717 cp = (new Path(getProject())).concatSystemClasspath("last");
718 }
719 loader = getProject().createClassLoader(cp);
720
721 //
722 // for classpath lookup we ignore the base directory
723 //
724 InputStream is
725 = loader.getResourceAsStream(matchingEntry.getLocation());
726
727 if (is != null) {
728 source = new InputSource(is);
729 URL entryURL = loader.getResource(matchingEntry.getLocation());
730 String sysid = entryURL.toExternalForm();
731 source.setSystemId(sysid);
732 log("catalog entry matched a resource in the classpath: '"
733 + sysid + "'", Project.MSG_DEBUG);
734 }
735
736 return source;
737 }
738
739 /**
740 * Utility method to lookup a ResourceLocation in URL-space.
741 *
742 * @return An InputSource for reading the resource, or <code>null</code>
743 * if the resource does not identify a valid URL or is not readable.
744 */
745 private InputSource urlLookup(ResourceLocation matchingEntry) {
746
747 String uri = matchingEntry.getLocation();
748 URL baseURL = null;
749
750 //
751 // The ResourceLocation may specify a relative url for its
752 // location attribute. This is resolved using the appropriate
753 // base.
754 //
755 if (matchingEntry.getBase() != null) {
756 baseURL = matchingEntry.getBase();
757 } else {
758 try {
759 baseURL = fileUtils.getFileURL(getProject().getBaseDir());
760 } catch (MalformedURLException ex) {
761 throw new BuildException("Project basedir cannot be converted to a URL");
762 }
763 }
764
765 InputSource source = null;
766 URL url = null;
767
768 try {
769 url = new URL(baseURL, uri);
770 } catch (MalformedURLException ex) {
771 // ignore
772 }
773
774 if (url != null) {
775 try {
776 InputStream is = url.openStream();
777 if (is != null) {
778 source = new InputSource(is);
779 String sysid = url.toExternalForm();
780 source.setSystemId(sysid);
781 log("catalog entry matched as a URL: '"
782 + sysid + "'", Project.MSG_DEBUG);
783 }
784 } catch (IOException ex) {
785 // ignore
786 }
787 }
788
789 return source;
790
791 }
792
793 /**
794 * Interface implemented by both the InternalResolver strategy and
795 * the ExternalResolver strategy.
796 */
797 private interface CatalogResolver extends URIResolver, EntityResolver {
798
799 InputSource resolveEntity(String publicId, String systemId);
800
801 Source resolve(String href, String base) throws TransformerException;
802 }
803
804 /**
805 * The InternalResolver strategy is used if the Apache resolver
806 * library (Norm Walsh's library from xml-commons) is not
807 * available. In this case, external catalog files will be
808 * ignored.
809 *
810 */
811 private class InternalResolver implements CatalogResolver {
812
813 public InternalResolver() {
814 log("Apache resolver library not found, internal resolver will be used",
815 Project.MSG_VERBOSE);
816 }
817
818 public InputSource resolveEntity(String publicId,
819 String systemId) {
820 InputSource result = null;
821 ResourceLocation matchingEntry = findMatchingEntry(publicId);
822
823 if (matchingEntry != null) {
824
825 log("Matching catalog entry found for publicId: '"
826 + matchingEntry.getPublicId() + "' location: '"
827 + matchingEntry.getLocation() + "'",
828 Project.MSG_DEBUG);
829
830 result = filesystemLookup(matchingEntry);
831
832 if (result == null) {
833 result = classpathLookup(matchingEntry);
834 }
835
836 if (result == null) {
837 result = urlLookup(matchingEntry);
838 }
839 }
840 return result;
841 }
842
843 public Source resolve(String href, String base)
844 throws TransformerException {
845
846 SAXSource result = null;
847 InputSource source = null;
848
849 ResourceLocation matchingEntry = findMatchingEntry(href);
850
851 if (matchingEntry != null) {
852
853 log("Matching catalog entry found for uri: '"
854 + matchingEntry.getPublicId() + "' location: '"
855 + matchingEntry.getLocation() + "'",
856 Project.MSG_DEBUG);
857
858 //
859 // Use the passed in base in preference to the base
860 // from matchingEntry, which is either null or the
861 // directory in which the external catalog file from
862 // which it was obtained is located. We make a copy
863 // so matchingEntry's original base is untouched.
864 //
865 // This is the standard behavior as per my reading of
866 // the JAXP and XML Catalog specs. CKS 11/7/2002
867 //
868 ResourceLocation entryCopy = matchingEntry;
869 if (base != null) {
870 try {
871 URL baseURL = new URL(base);
872 entryCopy = new ResourceLocation();
873 entryCopy.setBase(baseURL);
874 } catch (MalformedURLException ex) {
875 // ignore
876 }
877 }
878 entryCopy.setPublicId(matchingEntry.getPublicId());
879 entryCopy.setLocation(matchingEntry.getLocation());
880
881 source = filesystemLookup(entryCopy);
882
883 if (source == null) {
884 source = classpathLookup(entryCopy);
885 }
886
887 if (source == null) {
888 source = urlLookup(entryCopy);
889 }
890
891 if (source != null) {
892 result = new SAXSource(source);
893 }
894 }
895 return result;
896 }
897 }
898
899 /**
900 * The ExternalResolver strategy is used if the Apache resolver
901 * library (Norm Walsh's library from xml-commons) is available in
902 * the classpath. The ExternalResolver is a essentially a superset
903 * of the InternalResolver.
904 *
905 */
906 private class ExternalResolver implements CatalogResolver {
907
908 private Method setXMLCatalog = null;
909 private Method parseCatalog = null;
910 private Method resolveEntity = null;
911 private Method resolve = null;
912
913 /** The instance of the ApacheCatalogResolver bridge class */
914 private Object resolverImpl = null;
915
916 private boolean externalCatalogsProcessed = false;
917
918 public ExternalResolver(Class resolverImplClass,
919 Object resolverImpl) {
920
921 this.resolverImpl = resolverImpl;
922
923 //
924 // Get Method instances for each of the methods we need to
925 // call on the resolverImpl using reflection. We can't
926 // call them directly, because they require on the
927 // xml-commons resolver library which may not be available
928 // in the classpath.
929 //
930 try {
931 setXMLCatalog =
932 resolverImplClass.getMethod("setXMLCatalog",
933 new Class[] {XMLCatalog.class});
934
935 parseCatalog =
936 resolverImplClass.getMethod("parseCatalog",
937 new Class[] {String.class});
938
939 resolveEntity =
940 resolverImplClass.getMethod("resolveEntity",
941 new Class[] {String.class, String.class});
942
943 resolve =
944 resolverImplClass.getMethod("resolve",
945 new Class[] {String.class, String.class});
946 } catch (NoSuchMethodException ex) {
947 throw new BuildException(ex);
948 }
949
950 log("Apache resolver library found, xml-commons resolver will be used",
951 Project.MSG_VERBOSE);
952 }
953
954 public InputSource resolveEntity(String publicId,
955 String systemId) {
956 InputSource result = null;
957
958 processExternalCatalogs();
959
960 ResourceLocation matchingEntry = findMatchingEntry(publicId);
961
962 if (matchingEntry != null) {
963
964 log("Matching catalog entry found for publicId: '"
965 + matchingEntry.getPublicId() + "' location: '"
966 + matchingEntry.getLocation() + "'",
967 Project.MSG_DEBUG);
968
969 result = filesystemLookup(matchingEntry);
970
971 if (result == null) {
972 result = classpathLookup(matchingEntry);
973 }
974
975 if (result == null) {
976 try {
977 result =
978 (InputSource) resolveEntity.invoke(resolverImpl,
979 new Object[] {publicId, systemId});
980 } catch (Exception ex) {
981 throw new BuildException(ex);
982 }
983 }
984 } else {
985 //
986 // We didn't match a ResourceLocation, but since we
987 // only support PUBLIC and URI entry types internally,
988 // it is still possible that there is another entry in
989 // an external catalog that will match. We call
990 // Apache resolver's resolveEntity method to cover
991 // this possibility.
992 //
993 try {
994 result =
995 (InputSource) resolveEntity.invoke(resolverImpl,
996 new Object[] {publicId, systemId});
997 } catch (Exception ex) {
998 throw new BuildException(ex);
999 }
1000 }
1001
1002 return result;
1003 }
1004
1005 public Source resolve(String href, String base)
1006 throws TransformerException {
1007
1008 SAXSource result = null;
1009 InputSource source = null;
1010
1011 processExternalCatalogs();
1012
1013 ResourceLocation matchingEntry = findMatchingEntry(href);
1014
1015 if (matchingEntry != null) {
1016
1017 log("Matching catalog entry found for uri: '"
1018 + matchingEntry.getPublicId() + "' location: '"
1019 + matchingEntry.getLocation() + "'",
1020 Project.MSG_DEBUG);
1021
1022 //
1023 // Use the passed in base in preference to the base
1024 // from matchingEntry, which is either null or the
1025 // directory in which the external catalog file from
1026 // which it was obtained is located. We make a copy
1027 // so matchingEntry's original base is untouched. Of
1028 // course, if there is no base, no need to make a
1029 // copy...
1030 //
1031 // This is the standard behavior as per my reading of
1032 // the JAXP and XML Catalog specs. CKS 11/7/2002
1033 //
1034 ResourceLocation entryCopy = matchingEntry;
1035 if (base != null) {
1036 try {
1037 URL baseURL = new URL(base);
1038 entryCopy = new ResourceLocation();
1039 entryCopy.setBase(baseURL);
1040 } catch (MalformedURLException ex) {
1041 // ignore
1042 }
1043 }
1044 entryCopy.setPublicId(matchingEntry.getPublicId());
1045 entryCopy.setLocation(matchingEntry.getLocation());
1046
1047 source = filesystemLookup(entryCopy);
1048
1049 if (source == null) {
1050 source = classpathLookup(entryCopy);
1051 }
1052
1053 if (source != null) {
1054 result = new SAXSource(source);
1055 } else {
1056 try {
1057 result =
1058 (SAXSource) resolve.invoke(resolverImpl,
1059 new Object[] {href, base});
1060 } catch (Exception ex) {
1061 throw new BuildException(ex);
1062 }
1063 }
1064 } else {
1065 //
1066 // We didn't match a ResourceLocation, but since we
1067 // only support PUBLIC and URI entry types internally,
1068 // it is still possible that there is another entry in
1069 // an external catalog that will match. We call
1070 // Apache resolver's resolveEntity method to cover
1071 // this possibility.
1072 //
1073 try {
1074 result =
1075 (SAXSource) resolve.invoke(resolverImpl,
1076 new Object[] {href, base});
1077 } catch (Exception ex) {
1078 throw new BuildException(ex);
1079 }
1080 }
1081 return result;
1082 }
1083
1084 /**
1085 * Process each external catalog file specified in a
1086 * <code>&lt;catalogpath&gt;</code>. It will be
1087 * parsed by the resolver library, and the individual elements
1088 * will be added back to us (that is, the controlling
1089 * XMLCatalog instance) via a callback mechanism.
1090 */
1091 private void processExternalCatalogs() {
1092
1093 if (!externalCatalogsProcessed) {
1094
1095 try {
1096 setXMLCatalog.invoke(resolverImpl,
1097 new Object[] {XMLCatalog.this});
1098 } catch (Exception ex) {
1099 throw new BuildException(ex);
1100 }
1101
1102 // Parse each catalog listed in nested <catalogpath> elements
1103 Path catPath = getCatalogPath();
1104 if (catPath != null) {
1105 log("Using catalogpath '" + getCatalogPath() + "'",
1106 Project.MSG_DEBUG);
1107 String[] catPathList = getCatalogPath().list();
1108
1109 for (int i = 0; i < catPathList.length; i++) {
1110 File catFile = new File(catPathList[i]);
1111 log("Parsing " + catFile, Project.MSG_DEBUG);
1112 try {
1113 parseCatalog.invoke(resolverImpl,
1114 new Object[] {catFile.getPath()});
1115 } catch (Exception ex) {
1116 throw new BuildException(ex);
1117 }
1118 }
1119 }
1120 }
1121 externalCatalogsProcessed = true;
1122 }
1123 }
1124} //-- XMLCatalog
Note: See TracBrowser for help on using the repository browser.