KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > types > XMLCatalog


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.types;
20
21 import java.lang.reflect.Method JavaDoc;
22
23 import java.io.File JavaDoc;
24 import java.io.FileInputStream JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.net.MalformedURLException JavaDoc;
28 import java.net.URL JavaDoc;
29 import java.util.Enumeration JavaDoc;
30 import java.util.Vector JavaDoc;
31 import javax.xml.parsers.ParserConfigurationException JavaDoc;
32 import javax.xml.parsers.SAXParserFactory JavaDoc;
33 import javax.xml.transform.Source JavaDoc;
34 import javax.xml.transform.TransformerException JavaDoc;
35 import javax.xml.transform.URIResolver JavaDoc;
36 import javax.xml.transform.sax.SAXSource JavaDoc;
37 import org.apache.tools.ant.AntClassLoader;
38 import org.apache.tools.ant.BuildException;
39 import org.apache.tools.ant.Project;
40 import org.apache.tools.ant.util.FileUtils;
41 import org.apache.tools.ant.util.JAXPUtils;
42 import org.xml.sax.EntityResolver JavaDoc;
43 import org.xml.sax.InputSource JavaDoc;
44 import org.xml.sax.SAXException JavaDoc;
45 import org.xml.sax.XMLReader JavaDoc;
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  */

119 public class XMLCatalog extends DataType
120     implements Cloneable JavaDoc, EntityResolver JavaDoc, URIResolver JavaDoc {
121
122     /** helper for some File.toURL connversions */
123     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
124
125     //-- Fields ----------------------------------------------------------------
126

127     /** Holds dtd/entity objects until needed. */
128     private Vector JavaDoc elements = new Vector JavaDoc();
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 JavaDoc APACHE_RESOLVER
146         = "org.apache.tools.ant.types.resolver.ApacheCatalogResolver";
147
148     /**
149      * Resolver base class
150      */

151     public static final String JavaDoc 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 JavaDoc 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      * Allows nested classpath elements. Not allowed if this catalog
183      * is itself a reference to another catalog -- that is, a catalog
184      * cannot both refer to another <em>and</em> contain elements or
185      * other attributes.
186      *
187      * @return a Path instance to be configured.
188      */

189     public Path createClasspath() {
190         if (isReference()) {
191             throw noChildrenAllowed();
192         }
193         if (this.classpath == null) {
194             this.classpath = new Path(getProject());
195         }
196         setChecked(false);
197         return this.classpath.createPath();
198     }
199
200     /**
201      * Allows simple classpath string. Not allowed if this catalog is
202      * itself a reference to another catalog -- that is, a catalog
203      * cannot both refer to another <em>and</em> contain elements or
204      * other attributes.
205      *
206      * @param classpath the classpath to use to look up entities.
207      */

208     public void setClasspath(Path classpath) {
209         if (isReference()) {
210             throw tooManyAttributes();
211         }
212         if (this.classpath == null) {
213             this.classpath = classpath;
214         } else {
215             this.classpath.append(classpath);
216         }
217         setChecked(false);
218     }
219
220     /**
221      * Allows classpath reference. Not allowed if this catalog is
222      * itself a reference to another catalog -- that is, a catalog
223      * cannot both refer to another <em>and</em> contain elements or
224      * other attributes.
225      *
226      * @param r an Ant reference containing a classpath.
227      */

228     public void setClasspathRef(Reference r) {
229         if (isReference()) {
230             throw tooManyAttributes();
231         }
232         createClasspath().setRefid(r);
233         setChecked(false);
234     }
235
236     /** Creates a nested <code>&lt;catalogpath&gt;</code> element.
237      * Not allowed if this catalog is itself a reference to another
238      * catalog -- that is, a catalog cannot both refer to another
239      * <em>and</em> contain elements or other attributes.
240      *
241      * @return a path to be configured as the catalog path.
242      * @exception BuildException
243      * if this is a reference and no nested elements are allowed.
244      */

245     public Path createCatalogPath() {
246         if (isReference()) {
247             throw noChildrenAllowed();
248         }
249         if (this.catalogPath == null) {
250             this.catalogPath = new Path(getProject());
251         }
252         setChecked(false);
253         return this.catalogPath.createPath();
254     }
255
256     /**
257      * Allows catalogpath reference. Not allowed if this catalog is
258      * itself a reference to another catalog -- that is, a catalog
259      * cannot both refer to another <em>and</em> contain elements or
260      * other attributes.
261      *
262      * @param r an Ant reference containing a classpath to be used as
263      * the catalog path.
264      */

265     public void setCatalogPathRef(Reference r) {
266         if (isReference()) {
267             throw tooManyAttributes();
268         }
269         createCatalogPath().setRefid(r);
270         setChecked(false);
271     }
272
273
274     /**
275      * Returns the catalog path in which to attempt to resolve DTDs.
276      *
277      * @return the catalog path
278      */

279     public Path getCatalogPath() {
280         return getRef().catalogPath;
281     }
282
283
284     /**
285      * Creates the nested <code>&lt;dtd&gt;</code> element. Not
286      * allowed if this catalog is itself a reference to another
287      * catalog -- that is, a catalog cannot both refer to another
288      * <em>and</em> contain elements or other attributes.
289      *
290      * @param dtd the information about the PUBLIC resource mapping to
291      * be added to the catalog
292      * @exception BuildException if this is a reference and no nested
293      * elements are allowed.
294      */

295     public void addDTD(ResourceLocation dtd) throws BuildException {
296         if (isReference()) {
297             throw noChildrenAllowed();
298         }
299
300         getElements().addElement(dtd);
301         setChecked(false);
302     }
303
304     /**
305      * Creates the nested <code>&lt;entity&gt;</code> element. Not
306      * allowed if this catalog is itself a reference to another
307      * catalog -- that is, a catalog cannot both refer to another
308      * <em>and</em> contain elements or other attributes.
309      *
310      * @param entity the information about the URI resource mapping to be
311      * added to the catalog.
312      * @exception BuildException if this is a reference and no nested
313      * elements are allowed.
314      */

315     public void addEntity(ResourceLocation entity) throws BuildException {
316         addDTD(entity);
317     }
318
319     /**
320      * Loads a nested <code>&lt;xmlcatalog&gt;</code> into our
321      * definition. Not allowed if this catalog is itself a reference
322      * to another catalog -- that is, a catalog cannot both refer to
323      * another <em>and</em> contain elements or other attributes.
324      *
325      * @param catalog Nested XMLCatalog
326      */

327     public void addConfiguredXMLCatalog(XMLCatalog catalog) {
328         if (isReference()) {
329             throw noChildrenAllowed();
330         }
331
332         // Add all nested elements to our catalog
333
Vector JavaDoc newElements = catalog.getElements();
334         Vector JavaDoc ourElements = getElements();
335         Enumeration JavaDoc e = newElements.elements();
336         while (e.hasMoreElements()) {
337             ourElements.addElement(e.nextElement());
338         }
339
340         // Append the classpath of the nested catalog
341
Path nestedClasspath = catalog.getClasspath();
342         createClasspath().append(nestedClasspath);
343
344         // Append the catalog path of the nested catalog
345
Path nestedCatalogPath = catalog.getCatalogPath();
346         createCatalogPath().append(nestedCatalogPath);
347         setChecked(false);
348     }
349
350     /**
351      * Makes this instance in effect a reference to another XMLCatalog
352      * instance.
353      *
354      * <p>You must not set another attribute or nest elements inside
355      * this element if you make it a reference. That is, a catalog
356      * cannot both refer to another <em>and</em> contain elements or
357      * attributes.</p>
358      *
359      * @param r the reference to which this catalog instance is associated
360      * @exception BuildException if this instance already has been configured.
361      */

362     public void setRefid(Reference r) throws BuildException {
363         if (!elements.isEmpty()) {
364             throw tooManyAttributes();
365         }
366         super.setRefid(r);
367     }
368
369     /**
370      * Implements the EntityResolver.resolveEntity() interface method.
371      * @param publicId the public id to resolve.
372      * @param systemId the system id to resolve.
373      * @throws SAXException if there is a parsing problem.
374      * @throws IOException if there is an IO problem.
375      * @return the resolved entity.
376      * @see org.xml.sax.EntityResolver#resolveEntity
377      */

378     public InputSource JavaDoc resolveEntity(String JavaDoc publicId, String JavaDoc systemId)
379         throws SAXException JavaDoc, IOException JavaDoc {
380
381         if (isReference()) {
382             return getRef().resolveEntity(publicId, systemId);
383         }
384
385         dieOnCircularReference();
386
387         log("resolveEntity: '" + publicId + "': '" + systemId + "'",
388             Project.MSG_DEBUG);
389
390         InputSource JavaDoc inputSource =
391             getCatalogResolver().resolveEntity(publicId, systemId);
392
393         if (inputSource == null) {
394             log("No matching catalog entry found, parser will use: '"
395                 + systemId + "'", Project.MSG_DEBUG);
396         }
397
398         return inputSource;
399     }
400
401     /**
402      * Implements the URIResolver.resolve() interface method.
403      * @param href an href attribute.
404      * @param base the base URI.
405      * @return a Source object, or null if href cannot be resolved.
406      * @throws TransformerException if an error occurs.
407      * @see javax.xml.transform.URIResolver#resolve
408      */

409     public Source JavaDoc resolve(String JavaDoc href, String JavaDoc base)
410         throws TransformerException JavaDoc {
411
412         if (isReference()) {
413             return getRef().resolve(href, base);
414         }
415
416         dieOnCircularReference();
417
418         SAXSource JavaDoc source = null;
419
420         String JavaDoc uri = removeFragment(href);
421
422         log("resolve: '" + uri + "' with base: '" + base + "'", Project.MSG_DEBUG);
423
424         source = (SAXSource JavaDoc) getCatalogResolver().resolve(uri, base);
425
426         if (source == null) {
427             log("No matching catalog entry found, parser will use: '"
428                 + href + "'", Project.MSG_DEBUG);
429             //
430
// Cannot return a null source, because we have to call
431
// setEntityResolver (see setEntityResolver javadoc comment)
432
//
433
source = new SAXSource JavaDoc();
434             URL JavaDoc baseURL = null;
435             try {
436                 if (base == null) {
437                     baseURL = FILE_UTILS.getFileURL(getProject().getBaseDir());
438                 } else {
439                     baseURL = new URL JavaDoc(base);
440                 }
441                 URL JavaDoc url = (uri.length() == 0 ? baseURL : new URL JavaDoc(baseURL, uri));
442                 source.setInputSource(new InputSource JavaDoc(url.toString()));
443             } catch (MalformedURLException JavaDoc ex) {
444                 // At this point we are probably in failure mode, but
445
// try to use the bare URI as a last gasp
446
source.setInputSource(new InputSource JavaDoc(uri));
447             }
448         }
449
450         setEntityResolver(source);
451         return source;
452     }
453
454     /**
455      * @since Ant 1.6
456      */

457     private XMLCatalog getRef() {
458         if (!isReference()) {
459             return this;
460         }
461         return (XMLCatalog) getCheckedRef(XMLCatalog.class, "xmlcatalog");
462     }
463
464     /**
465      * The instance of the CatalogResolver strategy to use.
466      */

467     private CatalogResolver catalogResolver = null;
468
469     /**
470      * Factory method for creating the appropriate CatalogResolver
471      * strategy implementation.
472      * <p> Until we query the classpath, we don't know whether the Apache
473      * resolver (Norm Walsh's library from xml-commons) is available or not.
474      * This method determines whether the library is available and creates the
475      * appropriate implementation of CatalogResolver based on the answer.</p>
476      * <p>This is an application of the Gang of Four Strategy Pattern
477      * combined with Template Method.</p>
478      */

479     private CatalogResolver getCatalogResolver() {
480
481         if (catalogResolver == null) {
482
483             AntClassLoader loader = null;
484
485             loader = getProject().createClassLoader(Path.systemClasspath);
486
487             try {
488                 Class JavaDoc clazz = Class.forName(APACHE_RESOLVER, true, loader);
489
490                 // The Apache resolver is present - Need to check if it can
491
// be seen by the catalog resolver class. Start by getting
492
// the actual loader
493
ClassLoader JavaDoc apacheResolverLoader = clazz.getClassLoader();
494
495                 // load the base class through this loader.
496
Class JavaDoc baseResolverClass
497                     = Class.forName(CATALOG_RESOLVER, true, apacheResolverLoader);
498
499                 // and find its actual loader
500
ClassLoader JavaDoc baseResolverLoader
501                     = baseResolverClass.getClassLoader();
502
503                 // We have the loader which is being used to load the
504
// CatalogResolver. Can it see the ApacheResolver? The
505
// base resolver will only be able to create the ApacheResolver
506
// if it can see it - doesn't use the context loader.
507
clazz = Class.forName(APACHE_RESOLVER, true, baseResolverLoader);
508
509                 Object JavaDoc obj = clazz.newInstance();
510                 //
511
// Success! The xml-commons resolver library is
512
// available, so use it.
513
//
514
catalogResolver = new ExternalResolver(clazz, obj);
515             } catch (Throwable JavaDoc ex) {
516                 //
517
// The xml-commons resolver library is not
518
// available, so we can't use it.
519
//
520
catalogResolver = new InternalResolver();
521                 if (getCatalogPath() != null
522                     && getCatalogPath().list().length != 0) {
523                         log("Warning: XML resolver not found; external catalogs"
524                             + " will be ignored", Project.MSG_WARN);
525                     }
526                 log("Failed to load Apache resolver: " + ex, Project.MSG_DEBUG);
527             }
528         }
529         return catalogResolver;
530     }
531
532     /**
533      * <p>This is called from the URIResolver to set an EntityResolver
534      * on the SAX parser to be used for new XML documents that are
535      * encountered as a result of the document() function, xsl:import,
536      * or xsl:include. This is done because the XSLT processor calls
537      * out to the SAXParserFactory itself to create a new SAXParser to
538      * parse the new document. The new parser does not automatically
539      * inherit the EntityResolver of the original (although arguably
540      * it should). See below:</p>
541      *
542      * <tt>"If an application wants to set the ErrorHandler or
543      * EntityResolver for an XMLReader used during a transformation,
544      * it should use a URIResolver to return the SAXSource which
545      * provides (with getXMLReader) a reference to the XMLReader"</tt>
546      *
547      * <p>...quoted from page 118 of the Java API for XML
548      * Processing 1.1 specification</p>
549      *
550      */

551     private void setEntityResolver(SAXSource JavaDoc source) throws TransformerException JavaDoc {
552
553         XMLReader JavaDoc reader = source.getXMLReader();
554         if (reader == null) {
555             SAXParserFactory JavaDoc spFactory = SAXParserFactory.newInstance();
556             spFactory.setNamespaceAware(true);
557             try {
558                 reader = spFactory.newSAXParser().getXMLReader();
559             } catch (ParserConfigurationException JavaDoc ex) {
560                 throw new TransformerException JavaDoc(ex);
561             } catch (SAXException JavaDoc ex) {
562                 throw new TransformerException JavaDoc(ex);
563             }
564         }
565         reader.setEntityResolver(this);
566         source.setXMLReader(reader);
567     }
568
569     /**
570      * Find a ResourceLocation instance for the given publicId.
571      *
572      * @param publicId the publicId of the Resource for which local information
573      * is required.
574      * @return a ResourceLocation instance with information on the local location
575      * of the Resource or null if no such information is available.
576      */

577     private ResourceLocation findMatchingEntry(String JavaDoc publicId) {
578         Enumeration JavaDoc e = getElements().elements();
579         ResourceLocation element = null;
580         while (e.hasMoreElements()) {
581             Object JavaDoc o = e.nextElement();
582             if (o instanceof ResourceLocation) {
583                 element = (ResourceLocation) o;
584                 if (element.getPublicId().equals(publicId)) {
585                     return element;
586                 }
587             }
588         }
589         return null;
590     }
591
592     /**
593      * Utility method to remove trailing fragment from a URI.
594      * For example,
595      * <code>http://java.sun.com/index.html#chapter1</code>
596      * would return <code>http://java.sun.com/index.html</code>.
597      *
598      * @param uri The URI to process. It may or may not contain a
599      * fragment.
600      * @return The URI sans fragment.
601      */

602     private String JavaDoc removeFragment(String JavaDoc uri) {
603         String JavaDoc result = uri;
604         int hashPos = uri.indexOf("#");
605         if (hashPos >= 0) {
606             result = uri.substring(0, hashPos);
607         }
608         return result;
609     }
610
611     /**
612      * Utility method to lookup a ResourceLocation in the filesystem.
613      *
614      * @return An InputSource for reading the file, or <code>null</code>
615      * if the file does not exist or is not readable.
616      */

617     private InputSource JavaDoc filesystemLookup(ResourceLocation matchingEntry) {
618
619         String JavaDoc uri = matchingEntry.getLocation();
620         // the following line seems to be necessary on Windows under JDK 1.2
621
uri = uri.replace(File.separatorChar, '/');
622         URL JavaDoc baseURL = null;
623
624         //
625
// The ResourceLocation may specify a relative path for its
626
// location attribute. This is resolved using the appropriate
627
// base.
628
//
629
if (matchingEntry.getBase() != null) {
630             baseURL = matchingEntry.getBase();
631         } else {
632             try {
633                 baseURL = FILE_UTILS.getFileURL(getProject().getBaseDir());
634             } catch (MalformedURLException JavaDoc ex) {
635                 throw new BuildException("Project basedir cannot be converted to a URL");
636             }
637         }
638
639         InputSource JavaDoc source = null;
640         URL JavaDoc url = null;
641         try {
642             url = new URL JavaDoc(baseURL, uri);
643         } catch (MalformedURLException JavaDoc ex) {
644             // this processing is useful under Windows when the location of the DTD
645
// has been given as an absolute path
646
// see Bugzilla Report 23913
647
File JavaDoc testFile = new File JavaDoc(uri);
648             if (testFile.exists() && testFile.canRead()) {
649                 log("uri : '"
650                     + uri + "' matches a readable file", Project.MSG_DEBUG);
651                 try {
652                     url = FILE_UTILS.getFileURL(testFile);
653                 } catch (MalformedURLException JavaDoc ex1) {
654                     throw new BuildException(
655                         "could not find an URL for :" + testFile.getAbsolutePath());
656                 }
657             } else {
658                 log("uri : '"
659                     + uri + "' does not match a readable file", Project.MSG_DEBUG);
660
661             }
662         }
663
664         if (url != null && url.getProtocol().equals("file")) {
665             String JavaDoc fileName = FILE_UTILS.fromURI(url.toString());
666             if (fileName != null) {
667                 log("fileName " + fileName, Project.MSG_DEBUG);
668                 File JavaDoc resFile = new File JavaDoc(fileName);
669                 if (resFile.exists() && resFile.canRead()) {
670                     try {
671                         source = new InputSource JavaDoc(new FileInputStream JavaDoc(resFile));
672                         String JavaDoc sysid = JAXPUtils.getSystemId(resFile);
673                         source.setSystemId(sysid);
674                         log("catalog entry matched a readable file: '"
675                             + sysid + "'", Project.MSG_DEBUG);
676                     } catch (IOException JavaDoc ex) {
677                         // ignore
678
}
679                 }
680             }
681         }
682         return source;
683     }
684
685     /**
686      * Utility method to lookup a ResourceLocation in the classpath.
687      *
688      * @return An InputSource for reading the resource, or <code>null</code>
689      * if the resource does not exist in the classpath or is not readable.
690      */

691     private InputSource JavaDoc classpathLookup(ResourceLocation matchingEntry) {
692
693         InputSource JavaDoc source = null;
694
695         AntClassLoader loader = null;
696         Path cp = classpath;
697         if (cp != null) {
698             cp = classpath.concatSystemClasspath("ignore");
699         } else {
700             cp = (new Path(getProject())).concatSystemClasspath("last");
701         }
702         loader = getProject().createClassLoader(cp);
703
704         //
705
// for classpath lookup we ignore the base directory
706
//
707
InputStream JavaDoc is
708             = loader.getResourceAsStream(matchingEntry.getLocation());
709
710         if (is != null) {
711             source = new InputSource JavaDoc(is);
712             URL JavaDoc entryURL = loader.getResource(matchingEntry.getLocation());
713             String JavaDoc sysid = entryURL.toExternalForm();
714             source.setSystemId(sysid);
715             log("catalog entry matched a resource in the classpath: '"
716                 + sysid + "'", Project.MSG_DEBUG);
717         }
718
719         return source;
720     }
721
722     /**
723      * Utility method to lookup a ResourceLocation in URL-space.
724      *
725      * @return An InputSource for reading the resource, or <code>null</code>
726      * if the resource does not identify a valid URL or is not readable.
727      */

728     private InputSource JavaDoc urlLookup(ResourceLocation matchingEntry) {
729
730         String JavaDoc uri = matchingEntry.getLocation();
731         URL JavaDoc baseURL = null;
732
733         //
734
// The ResourceLocation may specify a relative url for its
735
// location attribute. This is resolved using the appropriate
736
// base.
737
//
738
if (matchingEntry.getBase() != null) {
739             baseURL = matchingEntry.getBase();
740         } else {
741             try {
742                 baseURL = FILE_UTILS.getFileURL(getProject().getBaseDir());
743             } catch (MalformedURLException JavaDoc ex) {
744                 throw new BuildException("Project basedir cannot be converted to a URL");
745             }
746         }
747
748         InputSource JavaDoc source = null;
749         URL JavaDoc url = null;
750
751         try {
752             url = new URL JavaDoc(baseURL, uri);
753         } catch (MalformedURLException JavaDoc ex) {
754             // ignore
755
}
756
757         if (url != null) {
758             try {
759                 InputStream JavaDoc is = url.openStream();
760                 if (is != null) {
761                     source = new InputSource JavaDoc(is);
762                     String JavaDoc sysid = url.toExternalForm();
763                     source.setSystemId(sysid);
764                     log("catalog entry matched as a URL: '"
765                         + sysid + "'", Project.MSG_DEBUG);
766                 }
767             } catch (IOException JavaDoc ex) {
768                 // ignore
769
}
770         }
771
772         return source;
773
774     }
775
776     /**
777      * Interface implemented by both the InternalResolver strategy and
778      * the ExternalResolver strategy.
779      */

780     private interface CatalogResolver extends URIResolver JavaDoc, EntityResolver JavaDoc {
781
782         InputSource JavaDoc resolveEntity(String JavaDoc publicId, String JavaDoc systemId);
783
784         Source JavaDoc resolve(String JavaDoc href, String JavaDoc base) throws TransformerException JavaDoc;
785     }
786
787     /**
788      * The InternalResolver strategy is used if the Apache resolver
789      * library (Norm Walsh's library from xml-commons) is not
790      * available. In this case, external catalog files will be
791      * ignored.
792      *
793      */

794     private class InternalResolver implements CatalogResolver {
795
796         public InternalResolver() {
797             log("Apache resolver library not found, internal resolver will be used",
798                 Project.MSG_VERBOSE);
799         }
800
801         public InputSource JavaDoc resolveEntity(String JavaDoc publicId,
802                                          String JavaDoc systemId) {
803             InputSource JavaDoc result = null;
804             ResourceLocation matchingEntry = findMatchingEntry(publicId);
805
806             if (matchingEntry != null) {
807
808                 log("Matching catalog entry found for publicId: '"
809                     + matchingEntry.getPublicId() + "' location: '"
810                     + matchingEntry.getLocation() + "'",
811                     Project.MSG_DEBUG);
812
813                 result = filesystemLookup(matchingEntry);
814
815                 if (result == null) {
816                     result = classpathLookup(matchingEntry);
817                 }
818
819                 if (result == null) {
820                     result = urlLookup(matchingEntry);
821                 }
822             }
823             return result;
824         }
825
826         public Source JavaDoc resolve(String JavaDoc href, String JavaDoc base)
827             throws TransformerException JavaDoc {
828
829             SAXSource JavaDoc result = null;
830             InputSource JavaDoc source = null;
831
832             ResourceLocation matchingEntry = findMatchingEntry(href);
833
834             if (matchingEntry != null) {
835
836                 log("Matching catalog entry found for uri: '"
837                     + matchingEntry.getPublicId() + "' location: '"
838                     + matchingEntry.getLocation() + "'",
839                     Project.MSG_DEBUG);
840
841                 //
842
// Use the passed in base in preference to the base
843
// from matchingEntry, which is either null or the
844
// directory in which the external catalog file from
845
// which it was obtained is located. We make a copy
846
// so matchingEntry's original base is untouched.
847
//
848
// This is the standard behavior as per my reading of
849
// the JAXP and XML Catalog specs. CKS 11/7/2002
850
//
851
ResourceLocation entryCopy = matchingEntry;
852                 if (base != null) {
853                     try {
854                         URL JavaDoc baseURL = new URL JavaDoc(base);
855                         entryCopy = new ResourceLocation();
856                         entryCopy.setBase(baseURL);
857                     } catch (MalformedURLException JavaDoc ex) {
858                         // ignore
859
}
860                 }
861                 entryCopy.setPublicId(matchingEntry.getPublicId());
862                 entryCopy.setLocation(matchingEntry.getLocation());
863
864                 source = filesystemLookup(entryCopy);
865
866                 if (source == null) {
867                     source = classpathLookup(entryCopy);
868                 }
869
870                 if (source == null) {
871                     source = urlLookup(entryCopy);
872                 }
873
874                 if (source != null) {
875                     result = new SAXSource JavaDoc(source);
876                 }
877             }
878             return result;
879         }
880     }
881
882     /**
883      * The ExternalResolver strategy is used if the Apache resolver
884      * library (Norm Walsh's library from xml-commons) is available in
885      * the classpath. The ExternalResolver is a essentially a superset
886      * of the InternalResolver.
887      *
888      */

889     private class ExternalResolver implements CatalogResolver {
890
891         private Method JavaDoc setXMLCatalog = null;
892         private Method JavaDoc parseCatalog = null;
893         private Method JavaDoc resolveEntity = null;
894         private Method JavaDoc resolve = null;
895
896         /** The instance of the ApacheCatalogResolver bridge class */
897         private Object JavaDoc resolverImpl = null;
898
899         private boolean externalCatalogsProcessed = false;
900
901         public ExternalResolver(Class JavaDoc resolverImplClass,
902                               Object JavaDoc resolverImpl) {
903
904             this.resolverImpl = resolverImpl;
905
906             //
907
// Get Method instances for each of the methods we need to
908
// call on the resolverImpl using reflection. We can't
909
// call them directly, because they require on the
910
// xml-commons resolver library which may not be available
911
// in the classpath.
912
//
913
try {
914                 setXMLCatalog =
915                     resolverImplClass.getMethod("setXMLCatalog",
916                                                 new Class JavaDoc[] {XMLCatalog.class});
917
918                 parseCatalog =
919                     resolverImplClass.getMethod("parseCatalog",
920                                                 new Class JavaDoc[] {String JavaDoc.class});
921
922                 resolveEntity =
923                     resolverImplClass.getMethod("resolveEntity",
924                                                 new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class});
925
926                 resolve =
927                     resolverImplClass.getMethod("resolve",
928                                                 new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class});
929             } catch (NoSuchMethodException JavaDoc ex) {
930                 throw new BuildException(ex);
931             }
932
933             log("Apache resolver library found, xml-commons resolver will be used",
934                 Project.MSG_VERBOSE);
935         }
936
937         public InputSource JavaDoc resolveEntity(String JavaDoc publicId,
938                                          String JavaDoc systemId) {
939             InputSource JavaDoc result = null;
940
941             processExternalCatalogs();
942
943             ResourceLocation matchingEntry = findMatchingEntry(publicId);
944
945             if (matchingEntry != null) {
946
947                 log("Matching catalog entry found for publicId: '"
948                     + matchingEntry.getPublicId() + "' location: '"
949                     + matchingEntry.getLocation() + "'",
950                     Project.MSG_DEBUG);
951
952                 result = filesystemLookup(matchingEntry);
953
954                 if (result == null) {
955                     result = classpathLookup(matchingEntry);
956                 }
957
958                 if (result == null) {
959                     try {
960                         result =
961                             (InputSource JavaDoc) resolveEntity.invoke(resolverImpl,
962                                                               new Object JavaDoc[] {publicId, systemId});
963                     } catch (Exception JavaDoc ex) {
964                         throw new BuildException(ex);
965                     }
966                 }
967             } else {
968                 //
969
// We didn't match a ResourceLocation, but since we
970
// only support PUBLIC and URI entry types internally,
971
// it is still possible that there is another entry in
972
// an external catalog that will match. We call
973
// Apache resolver's resolveEntity method to cover
974
// this possibility.
975
//
976
try {
977                     result =
978                         (InputSource JavaDoc) resolveEntity.invoke(resolverImpl,
979                                                           new Object JavaDoc[] {publicId, systemId});
980                 } catch (Exception JavaDoc ex) {
981                     throw new BuildException(ex);
982                 }
983             }
984
985             return result;
986         }
987
988         public Source JavaDoc resolve(String JavaDoc href, String JavaDoc base)
989             throws TransformerException JavaDoc {
990
991             SAXSource JavaDoc result = null;
992             InputSource JavaDoc source = null;
993
994             processExternalCatalogs();
995
996             ResourceLocation matchingEntry = findMatchingEntry(href);
997
998             if (matchingEntry != null) {
999
1000                log("Matching catalog entry found for uri: '"
1001                    + matchingEntry.getPublicId() + "' location: '"
1002                    + matchingEntry.getLocation() + "'",
1003                    Project.MSG_DEBUG);
1004
1005                //
1006
// Use the passed in base in preference to the base
1007
// from matchingEntry, which is either null or the
1008
// directory in which the external catalog file from
1009
// which it was obtained is located. We make a copy
1010
// so matchingEntry's original base is untouched. Of
1011
// course, if there is no base, no need to make a
1012
// copy...
1013
//
1014
// This is the standard behavior as per my reading of
1015
// the JAXP and XML Catalog specs. CKS 11/7/2002
1016
//
1017
ResourceLocation entryCopy = matchingEntry;
1018                if (base != null) {
1019                    try {
1020                        URL JavaDoc baseURL = new URL JavaDoc(base);
1021                        entryCopy = new ResourceLocation();
1022                        entryCopy.setBase(baseURL);
1023                    } catch (MalformedURLException JavaDoc ex) {
1024                        // ignore
1025
}
1026                }
1027                entryCopy.setPublicId(matchingEntry.getPublicId());
1028                entryCopy.setLocation(matchingEntry.getLocation());
1029
1030                source = filesystemLookup(entryCopy);
1031
1032                if (source == null) {
1033                    source = classpathLookup(entryCopy);
1034                }
1035
1036                if (source != null) {
1037                    result = new SAXSource JavaDoc(source);
1038                } else {
1039                    try {
1040                        result =
1041                            (SAXSource JavaDoc) resolve.invoke(resolverImpl,
1042                                                      new Object JavaDoc[] {href, base});
1043                    } catch (Exception JavaDoc ex) {
1044                        throw new BuildException(ex);
1045                    }
1046                }
1047            } else {
1048                //
1049
// We didn't match a ResourceLocation, but since we
1050
// only support PUBLIC and URI entry types internally,
1051
// it is still possible that there is another entry in
1052
// an external catalog that will match. We call
1053
// Apache resolver's resolveEntity method to cover
1054
// this possibility.
1055
//
1056
try {
1057                    result =
1058                        (SAXSource JavaDoc) resolve.invoke(resolverImpl,
1059                                                  new Object JavaDoc[] {href, base});
1060                } catch (Exception JavaDoc ex) {
1061                    throw new BuildException(ex);
1062                }
1063            }
1064            return result;
1065        }
1066
1067        /**
1068         * Process each external catalog file specified in a
1069         * <code>&lt;catalogpath&gt;</code>. It will be
1070         * parsed by the resolver library, and the individual elements
1071         * will be added back to us (that is, the controlling
1072         * XMLCatalog instance) via a callback mechanism.
1073         */

1074        private void processExternalCatalogs() {
1075
1076            if (!externalCatalogsProcessed) {
1077
1078                try {
1079                    setXMLCatalog.invoke(resolverImpl,
1080                                         new Object JavaDoc[] {XMLCatalog.this});
1081                } catch (Exception JavaDoc ex) {
1082                    throw new BuildException(ex);
1083                }
1084
1085                // Parse each catalog listed in nested <catalogpath> elements
1086
Path catPath = getCatalogPath();
1087                if (catPath != null) {
1088                    log("Using catalogpath '" + getCatalogPath() + "'",
1089                        Project.MSG_DEBUG);
1090                    String JavaDoc[] catPathList = getCatalogPath().list();
1091
1092                    for (int i = 0; i < catPathList.length; i++) {
1093                        File JavaDoc catFile = new File JavaDoc(catPathList[i]);
1094                        log("Parsing " + catFile, Project.MSG_DEBUG);
1095                        try {
1096                            parseCatalog.invoke(resolverImpl,
1097                                    new Object JavaDoc[] {catFile.getPath()});
1098                        } catch (Exception JavaDoc ex) {
1099                            throw new BuildException(ex);
1100                        }
1101                    }
1102                }
1103            }
1104            externalCatalogsProcessed = true;
1105        }
1106    }
1107} //-- XMLCatalog
1108
Popular Tags