diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/types/XMLCatalog.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/types/XMLCatalog.java | 1128 |
1 files changed, 1128 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/types/XMLCatalog.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/types/XMLCatalog.java new file mode 100644 index 00000000..bd9be431 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/types/XMLCatalog.java @@ -0,0 +1,1128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.types; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Stack; +import java.util.Vector; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.sax.SAXSource; + +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.JAXPUtils; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + + + +/** + * <p>This data type provides a catalog of resource locations (such as + * DTDs and XML entities), based on the <a + * href="http://oasis-open.org/committees/entity/spec-2001-08-06.html"> + * OASIS "Open Catalog" standard</a>. The catalog entries are used + * both for Entity resolution and URI resolution, in accordance with + * the {@link org.xml.sax.EntityResolver EntityResolver} and {@link + * javax.xml.transform.URIResolver URIResolver} interfaces as defined + * in the <a href="http://java.sun.com/xml/jaxp">Java API for XML + * Processing Specification</a>.</p> + * + * <p>Resource locations can be specified either in-line or in + * external catalog file(s), or both. In order to use an external + * catalog file, the xml-commons resolver library ("resolver.jar") + * must be in your classpath. External catalog files may be either <a + * href="http://oasis-open.org/committees/entity/background/9401.html"> + * plain text format</a> or <a + * href="http://www.oasis-open.org/committees/entity/spec-2001-08-06.html"> + * XML format</a>. If the xml-commons resolver library is not found + * in the classpath, external catalog files, specified in + * <code><catalogpath></code> paths, will be ignored and a warning will + * be logged. In this case, however, processing of inline entries will proceed + * normally.</p> + * + * <p>Currently, only <code><dtd></code> and + * <code><entity></code> elements may be specified inline; these + * correspond to OASIS catalog entry types <code>PUBLIC</code> and + * <code>URI</code> respectively.</p> + * + * <p>The following is a usage example:</p> + * + * <code> + * <xmlcatalog><br> + * <dtd publicId="" location="/path/to/file.jar" /><br> + * <dtd publicId="" location="/path/to/file2.jar" /><br> + * <entity publicId="" location="/path/to/file3.jar" /><br> + * <entity publicId="" location="/path/to/file4.jar" /><br> + * <catalogpath><br> + * <pathelement location="/etc/sgml/catalog"/><br> + * </catalogpath><br> + * <catalogfiles dir="/opt/catalogs/" includes="**\catalog.xml" /><br> + * </xmlcatalog><br> + * </code> + * <p> + * Tasks wishing to use <code><xmlcatalog></code> must provide a method called + * <code>createXMLCatalog</code> which returns an instance of + * <code>XMLCatalog</code>. Nested DTD and entity definitions are handled by + * the XMLCatalog object and must be labeled <code>dtd</code> and + * <code>entity</code> respectively.</p> + * + * <p>The following is a description of the resolution algorithm: + * entities/URIs/dtds are looked up in each of the following contexts, + * stopping when a valid and readable resource is found: + * <ol> + * <li>In the local filesystem</li> + * <li>In the classpath</li> + * <li>Using the Apache xml-commons resolver (if it is available)</li> + * <li>In URL-space</li> + * </ol> + * </p> + * + * <p>See {@link + * org.apache.tools.ant.taskdefs.optional.XMLValidateTask + * XMLValidateTask} for an example of a task that has integrated + * support for XMLCatalogs.</p> + * + * <p>Possible future extension could provide for additional OASIS + * entry types to be specified inline.</p> + * + */ +public class XMLCatalog extends DataType + implements Cloneable, EntityResolver, URIResolver { + + /** helper for some File.toURL connversions */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + //-- Fields ---------------------------------------------------------------- + + /** Holds dtd/entity objects until needed. */ + private Vector<ResourceLocation> elements = new Vector<ResourceLocation>(); + + /** + * Classpath in which to attempt to resolve resources. + */ + private Path classpath; + + /** + * Path listing external catalog files to search when resolving entities + */ + private Path catalogPath; + + /** + * The name of the bridge to the Apache xml-commons resolver + * class, used to determine whether resolver.jar is present in the + * classpath. + */ + public static final String APACHE_RESOLVER + = "org.apache.tools.ant.types.resolver.ApacheCatalogResolver"; + + /** + * Resolver base class + */ + public static final String CATALOG_RESOLVER + = "org.apache.xml.resolver.tools.CatalogResolver"; + + //-- Methods --------------------------------------------------------------- + + /** + * Default constructor + */ + public XMLCatalog() { + setChecked(false); + } + + /** + * Returns the elements of the catalog - ResourceLocation objects. + * + * @return the elements of the catalog - ResourceLocation objects + */ + private Vector<ResourceLocation> getElements() { + return getRef().elements; + } + + /** + * Returns the classpath in which to attempt to resolve resources. + * + * @return the classpath + */ + private Path getClasspath() { + return getRef().classpath; + } + + /** + * Allows nested classpath elements. Not allowed if this catalog + * is itself a reference to another catalog -- that is, a catalog + * cannot both refer to another <em>and</em> contain elements or + * other attributes. + * + * @return a Path instance to be configured. + */ + public Path createClasspath() { + if (isReference()) { + throw noChildrenAllowed(); + } + if (this.classpath == null) { + this.classpath = new Path(getProject()); + } + setChecked(false); + return this.classpath.createPath(); + } + + /** + * Allows simple classpath string. Not allowed if this catalog is + * itself a reference to another catalog -- that is, a catalog + * cannot both refer to another <em>and</em> contain elements or + * other attributes. + * + * @param classpath the classpath to use to look up entities. + */ + public void setClasspath(Path classpath) { + if (isReference()) { + throw tooManyAttributes(); + } + if (this.classpath == null) { + this.classpath = classpath; + } else { + this.classpath.append(classpath); + } + setChecked(false); + } + + /** + * Allows classpath reference. Not allowed if this catalog is + * itself a reference to another catalog -- that is, a catalog + * cannot both refer to another <em>and</em> contain elements or + * other attributes. + * + * @param r an Ant reference containing a classpath. + */ + public void setClasspathRef(Reference r) { + if (isReference()) { + throw tooManyAttributes(); + } + createClasspath().setRefid(r); + setChecked(false); + } + + /** Creates a nested <code><catalogpath></code> element. + * Not allowed if this catalog is itself a reference to another + * catalog -- that is, a catalog cannot both refer to another + * <em>and</em> contain elements or other attributes. + * + * @return a path to be configured as the catalog path. + * @exception BuildException + * if this is a reference and no nested elements are allowed. + */ + public Path createCatalogPath() { + if (isReference()) { + throw noChildrenAllowed(); + } + if (this.catalogPath == null) { + this.catalogPath = new Path(getProject()); + } + setChecked(false); + return this.catalogPath.createPath(); + } + + /** + * Allows catalogpath reference. Not allowed if this catalog is + * itself a reference to another catalog -- that is, a catalog + * cannot both refer to another <em>and</em> contain elements or + * other attributes. + * + * @param r an Ant reference containing a classpath to be used as + * the catalog path. + */ + public void setCatalogPathRef(Reference r) { + if (isReference()) { + throw tooManyAttributes(); + } + createCatalogPath().setRefid(r); + setChecked(false); + } + + + /** + * Returns the catalog path in which to attempt to resolve DTDs. + * + * @return the catalog path + */ + public Path getCatalogPath() { + return getRef().catalogPath; + } + + + /** + * Creates the nested <code><dtd></code> element. Not + * allowed if this catalog is itself a reference to another + * catalog -- that is, a catalog cannot both refer to another + * <em>and</em> contain elements or other attributes. + * + * @param dtd the information about the PUBLIC resource mapping to + * be added to the catalog + * @exception BuildException if this is a reference and no nested + * elements are allowed. + */ + public void addDTD(ResourceLocation dtd) throws BuildException { + if (isReference()) { + throw noChildrenAllowed(); + } + + getElements().addElement(dtd); + setChecked(false); + } + + /** + * Creates the nested <code><entity></code> element. Not + * allowed if this catalog is itself a reference to another + * catalog -- that is, a catalog cannot both refer to another + * <em>and</em> contain elements or other attributes. + * + * @param entity the information about the URI resource mapping to be + * added to the catalog. + * @exception BuildException if this is a reference and no nested + * elements are allowed. + */ + public void addEntity(ResourceLocation entity) throws BuildException { + addDTD(entity); + } + + /** + * Loads a nested <code><xmlcatalog></code> into our + * definition. Not allowed if this catalog is itself a reference + * to another catalog -- that is, a catalog cannot both refer to + * another <em>and</em> contain elements or other attributes. + * + * @param catalog Nested XMLCatalog + */ + public void addConfiguredXMLCatalog(XMLCatalog catalog) { + if (isReference()) { + throw noChildrenAllowed(); + } + + // Add all nested elements to our catalog + getElements().addAll(catalog.getElements()); + + // Append the classpath of the nested catalog + Path nestedClasspath = catalog.getClasspath(); + createClasspath().append(nestedClasspath); + + // Append the catalog path of the nested catalog + Path nestedCatalogPath = catalog.getCatalogPath(); + createCatalogPath().append(nestedCatalogPath); + setChecked(false); + } + + /** + * Makes this instance in effect a reference to another XMLCatalog + * instance. + * + * <p>You must not set another attribute or nest elements inside + * this element if you make it a reference. That is, a catalog + * cannot both refer to another <em>and</em> contain elements or + * attributes.</p> + * + * @param r the reference to which this catalog instance is associated + * @exception BuildException if this instance already has been configured. + */ + public void setRefid(Reference r) throws BuildException { + if (!elements.isEmpty()) { + throw tooManyAttributes(); + } + super.setRefid(r); + } + + /** + * Implements the EntityResolver.resolveEntity() interface method. + * @param publicId the public id to resolve. + * @param systemId the system id to resolve. + * @throws SAXException if there is a parsing problem. + * @throws IOException if there is an IO problem. + * @return the resolved entity. + * @see org.xml.sax.EntityResolver#resolveEntity + */ + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + + if (isReference()) { + return getRef().resolveEntity(publicId, systemId); + } + + dieOnCircularReference(); + + log("resolveEntity: '" + publicId + "': '" + systemId + "'", + Project.MSG_DEBUG); + + InputSource inputSource = + getCatalogResolver().resolveEntity(publicId, systemId); + + if (inputSource == null) { + log("No matching catalog entry found, parser will use: '" + + systemId + "'", Project.MSG_DEBUG); + } + + return inputSource; + } + + /** + * Implements the URIResolver.resolve() interface method. + * @param href an href attribute. + * @param base the base URI. + * @return a Source object, or null if href cannot be resolved. + * @throws TransformerException if an error occurs. + * @see javax.xml.transform.URIResolver#resolve + */ + public Source resolve(String href, String base) + throws TransformerException { + + if (isReference()) { + return getRef().resolve(href, base); + } + + dieOnCircularReference(); + + SAXSource source = null; + + String uri = removeFragment(href); + + log("resolve: '" + uri + "' with base: '" + base + "'", Project.MSG_DEBUG); + + source = (SAXSource) getCatalogResolver().resolve(uri, base); + + if (source == null) { + log("No matching catalog entry found, parser will use: '" + + href + "'", Project.MSG_DEBUG); + // + // Cannot return a null source, because we have to call + // setEntityResolver (see setEntityResolver javadoc comment) + // + source = new SAXSource(); + URL baseURL = null; + try { + if (base == null) { + baseURL = FILE_UTILS.getFileURL(getProject().getBaseDir()); + } else { + baseURL = new URL(base); + } + URL url = (uri.length() == 0 ? baseURL : new URL(baseURL, uri)); + source.setInputSource(new InputSource(url.toString())); + } catch (MalformedURLException ex) { + // At this point we are probably in failure mode, but + // try to use the bare URI as a last gasp + source.setInputSource(new InputSource(uri)); + } + } + + setEntityResolver(source); + return source; + } + + protected synchronized void dieOnCircularReference(Stack<Object> stk, Project p) + throws BuildException { + if (isChecked()) { + return; + } + if (isReference()) { + super.dieOnCircularReference(stk, p); + } else { + if (classpath != null) { + pushAndInvokeCircularReferenceCheck(classpath, stk, p); + } + if (catalogPath != null) { + pushAndInvokeCircularReferenceCheck(catalogPath, stk, p); + } + setChecked(true); + } + } + + /** + * @since Ant 1.6 + */ + private XMLCatalog getRef() { + if (!isReference()) { + return this; + } + return getCheckedRef(XMLCatalog.class, "xmlcatalog"); + } + + /** + * The instance of the CatalogResolver strategy to use. + */ + private CatalogResolver catalogResolver = null; + + /** + * Factory method for creating the appropriate CatalogResolver + * strategy implementation. + * <p> Until we query the classpath, we don't know whether the Apache + * resolver (Norm Walsh's library from xml-commons) is available or not. + * This method determines whether the library is available and creates the + * appropriate implementation of CatalogResolver based on the answer.</p> + * <p>This is an application of the Gang of Four Strategy Pattern + * combined with Template Method.</p> + */ + private CatalogResolver getCatalogResolver() { + + if (catalogResolver == null) { + + AntClassLoader loader = null; + // Memory-Leak in line below + loader = getProject().createClassLoader(Path.systemClasspath); + + try { + Class<?> clazz = Class.forName(APACHE_RESOLVER, true, loader); + + // The Apache resolver is present - Need to check if it can + // be seen by the catalog resolver class. Start by getting + // the actual loader + ClassLoader apacheResolverLoader = clazz.getClassLoader(); + + // load the base class through this loader. + Class<?> baseResolverClass + = Class.forName(CATALOG_RESOLVER, true, apacheResolverLoader); + + // and find its actual loader + ClassLoader baseResolverLoader + = baseResolverClass.getClassLoader(); + + // We have the loader which is being used to load the + // CatalogResolver. Can it see the ApacheResolver? The + // base resolver will only be able to create the ApacheResolver + // if it can see it - doesn't use the context loader. + clazz = Class.forName(APACHE_RESOLVER, true, baseResolverLoader); + + Object obj = clazz.newInstance(); + // + // Success! The xml-commons resolver library is + // available, so use it. + // + catalogResolver = new ExternalResolver(clazz, obj); + } catch (Throwable ex) { + // + // The xml-commons resolver library is not + // available, so we can't use it. + // + catalogResolver = new InternalResolver(); + if (getCatalogPath() != null + && getCatalogPath().list().length != 0) { + log("Warning: XML resolver not found; external catalogs" + + " will be ignored", Project.MSG_WARN); + } + log("Failed to load Apache resolver: " + ex, Project.MSG_DEBUG); + } + } + return catalogResolver; + } + + /** + * <p>This is called from the URIResolver to set an EntityResolver + * on the SAX parser to be used for new XML documents that are + * encountered as a result of the document() function, xsl:import, + * or xsl:include. This is done because the XSLT processor calls + * out to the SAXParserFactory itself to create a new SAXParser to + * parse the new document. The new parser does not automatically + * inherit the EntityResolver of the original (although arguably + * it should). See below:</p> + * + * <tt>"If an application wants to set the ErrorHandler or + * EntityResolver for an XMLReader used during a transformation, + * it should use a URIResolver to return the SAXSource which + * provides (with getXMLReader) a reference to the XMLReader"</tt> + * + * <p>...quoted from page 118 of the Java API for XML + * Processing 1.1 specification</p> + * + */ + private void setEntityResolver(SAXSource source) throws TransformerException { + + XMLReader reader = source.getXMLReader(); + if (reader == null) { + SAXParserFactory spFactory = SAXParserFactory.newInstance(); + spFactory.setNamespaceAware(true); + try { + reader = spFactory.newSAXParser().getXMLReader(); + } catch (ParserConfigurationException ex) { + throw new TransformerException(ex); + } catch (SAXException ex) { + throw new TransformerException(ex); + } + } + reader.setEntityResolver(this); + source.setXMLReader(reader); + } + + /** + * Find a ResourceLocation instance for the given publicId. + * + * @param publicId the publicId of the Resource for which local information + * is required. + * @return a ResourceLocation instance with information on the local location + * of the Resource or null if no such information is available. + */ + private ResourceLocation findMatchingEntry(String publicId) { + for (ResourceLocation element : getElements()) { + if (element.getPublicId().equals(publicId)) { + return element; + } + } + return null; + } + + /** + * Utility method to remove trailing fragment from a URI. + * For example, + * <code>http://java.sun.com/index.html#chapter1</code> + * would return <code>http://java.sun.com/index.html</code>. + * + * @param uri The URI to process. It may or may not contain a + * fragment. + * @return The URI sans fragment. + */ + private String removeFragment(String uri) { + String result = uri; + int hashPos = uri.indexOf("#"); + if (hashPos >= 0) { + result = uri.substring(0, hashPos); + } + return result; + } + + /** + * Utility method to lookup a ResourceLocation in the filesystem. + * + * @return An InputSource for reading the file, or <code>null</code> + * if the file does not exist or is not readable. + */ + private InputSource filesystemLookup(ResourceLocation matchingEntry) { + + String uri = matchingEntry.getLocation(); + // the following line seems to be necessary on Windows under JDK 1.2 + uri = uri.replace(File.separatorChar, '/'); + URL baseURL = null; + + // + // The ResourceLocation may specify a relative path for its + // location attribute. This is resolved using the appropriate + // base. + // + if (matchingEntry.getBase() != null) { + baseURL = matchingEntry.getBase(); + } else { + try { + baseURL = FILE_UTILS.getFileURL(getProject().getBaseDir()); + } catch (MalformedURLException ex) { + throw new BuildException("Project basedir cannot be converted to a URL"); + } + } + + InputSource source = null; + URL url = null; + try { + url = new URL(baseURL, uri); + } catch (MalformedURLException ex) { + // this processing is useful under Windows when the location of the DTD + // has been given as an absolute path + // see Bugzilla Report 23913 + File testFile = new File(uri); + if (testFile.exists() && testFile.canRead()) { + log("uri : '" + + uri + "' matches a readable file", Project.MSG_DEBUG); + try { + url = FILE_UTILS.getFileURL(testFile); + } catch (MalformedURLException ex1) { + throw new BuildException( + "could not find an URL for :" + testFile.getAbsolutePath()); + } + } else { + log("uri : '" + + uri + "' does not match a readable file", Project.MSG_DEBUG); + + } + } + + if (url != null && url.getProtocol().equals("file")) { + String fileName = FILE_UTILS.fromURI(url.toString()); + if (fileName != null) { + log("fileName " + fileName, Project.MSG_DEBUG); + File resFile = new File(fileName); + if (resFile.exists() && resFile.canRead()) { + try { + source = new InputSource(new FileInputStream(resFile)); + String sysid = JAXPUtils.getSystemId(resFile); + source.setSystemId(sysid); + log("catalog entry matched a readable file: '" + + sysid + "'", Project.MSG_DEBUG); + } catch (IOException ex) { + // ignore + } + } + } + } + return source; + } + + /** + * Utility method to lookup a ResourceLocation in the classpath. + * + * @return An InputSource for reading the resource, or <code>null</code> + * if the resource does not exist in the classpath or is not readable. + */ + private InputSource classpathLookup(ResourceLocation matchingEntry) { + + InputSource source = null; + + AntClassLoader loader = null; + Path cp = classpath; + if (cp != null) { + cp = classpath.concatSystemClasspath("ignore"); + } else { + cp = (new Path(getProject())).concatSystemClasspath("last"); + } + loader = getProject().createClassLoader(cp); + + // + // for classpath lookup we ignore the base directory + // + InputStream is + = loader.getResourceAsStream(matchingEntry.getLocation()); + + if (is != null) { + source = new InputSource(is); + URL entryURL = loader.getResource(matchingEntry.getLocation()); + String sysid = entryURL.toExternalForm(); + source.setSystemId(sysid); + log("catalog entry matched a resource in the classpath: '" + + sysid + "'", Project.MSG_DEBUG); + } + + return source; + } + + /** + * Utility method to lookup a ResourceLocation in URL-space. + * + * @return An InputSource for reading the resource, or <code>null</code> + * if the resource does not identify a valid URL or is not readable. + */ + private InputSource urlLookup(ResourceLocation matchingEntry) { + + String uri = matchingEntry.getLocation(); + URL baseURL = null; + + // + // The ResourceLocation may specify a relative url for its + // location attribute. This is resolved using the appropriate + // base. + // + if (matchingEntry.getBase() != null) { + baseURL = matchingEntry.getBase(); + } else { + try { + baseURL = FILE_UTILS.getFileURL(getProject().getBaseDir()); + } catch (MalformedURLException ex) { + throw new BuildException("Project basedir cannot be converted to a URL"); + } + } + + InputSource source = null; + URL url = null; + + try { + url = new URL(baseURL, uri); + } catch (MalformedURLException ex) { + // ignore + } + + if (url != null) { + try { + InputStream is = null; + URLConnection conn = url.openConnection(); + if (conn != null) { + conn.setUseCaches(false); + is = conn.getInputStream(); + } + if (is != null) { + source = new InputSource(is); + String sysid = url.toExternalForm(); + source.setSystemId(sysid); + log("catalog entry matched as a URL: '" + + sysid + "'", Project.MSG_DEBUG); + } + } catch (IOException ex) { + // ignore + } + } + + return source; + + } + + /** + * Interface implemented by both the InternalResolver strategy and + * the ExternalResolver strategy. + */ + private interface CatalogResolver extends URIResolver, EntityResolver { + + InputSource resolveEntity(String publicId, String systemId); + + Source resolve(String href, String base) throws TransformerException; + } + + /** + * The InternalResolver strategy is used if the Apache resolver + * library (Norm Walsh's library from xml-commons) is not + * available. In this case, external catalog files will be + * ignored. + * + */ + private class InternalResolver implements CatalogResolver { + + public InternalResolver() { + log("Apache resolver library not found, internal resolver will be used", + Project.MSG_VERBOSE); + } + + public InputSource resolveEntity(String publicId, + String systemId) { + InputSource result = null; + ResourceLocation matchingEntry = findMatchingEntry(publicId); + + if (matchingEntry != null) { + + log("Matching catalog entry found for publicId: '" + + matchingEntry.getPublicId() + "' location: '" + + matchingEntry.getLocation() + "'", + Project.MSG_DEBUG); + + result = filesystemLookup(matchingEntry); + + if (result == null) { + result = classpathLookup(matchingEntry); + } + + if (result == null) { + result = urlLookup(matchingEntry); + } + } + return result; + } + + public Source resolve(String href, String base) + throws TransformerException { + + SAXSource result = null; + InputSource source = null; + + ResourceLocation matchingEntry = findMatchingEntry(href); + + if (matchingEntry != null) { + + log("Matching catalog entry found for uri: '" + + matchingEntry.getPublicId() + "' location: '" + + matchingEntry.getLocation() + "'", + Project.MSG_DEBUG); + + // + // Use the passed in base in preference to the base + // from matchingEntry, which is either null or the + // directory in which the external catalog file from + // which it was obtained is located. We make a copy + // so matchingEntry's original base is untouched. + // + // This is the standard behavior as per my reading of + // the JAXP and XML Catalog specs. CKS 11/7/2002 + // + ResourceLocation entryCopy = matchingEntry; + if (base != null) { + try { + URL baseURL = new URL(base); + entryCopy = new ResourceLocation(); + entryCopy.setBase(baseURL); + } catch (MalformedURLException ex) { + // ignore + } + } + entryCopy.setPublicId(matchingEntry.getPublicId()); + entryCopy.setLocation(matchingEntry.getLocation()); + + source = filesystemLookup(entryCopy); + + if (source == null) { + source = classpathLookup(entryCopy); + } + + if (source == null) { + source = urlLookup(entryCopy); + } + + if (source != null) { + result = new SAXSource(source); + } + } + return result; + } + } + + /** + * The ExternalResolver strategy is used if the Apache resolver + * library (Norm Walsh's library from xml-commons) is available in + * the classpath. The ExternalResolver is a essentially a superset + * of the InternalResolver. + * + */ + private class ExternalResolver implements CatalogResolver { + + private Method setXMLCatalog = null; + private Method parseCatalog = null; + private Method resolveEntity = null; + private Method resolve = null; + + /** The instance of the ApacheCatalogResolver bridge class */ + private Object resolverImpl = null; + + private boolean externalCatalogsProcessed = false; + + public ExternalResolver(Class<?> resolverImplClass, + Object resolverImpl) { + + this.resolverImpl = resolverImpl; + + // + // Get Method instances for each of the methods we need to + // call on the resolverImpl using reflection. We can't + // call them directly, because they require on the + // xml-commons resolver library which may not be available + // in the classpath. + // + try { + setXMLCatalog = + resolverImplClass.getMethod("setXMLCatalog", + new Class[] {XMLCatalog.class}); + + parseCatalog = + resolverImplClass.getMethod("parseCatalog", + new Class[] {String.class}); + + resolveEntity = + resolverImplClass.getMethod("resolveEntity", + new Class[] {String.class, String.class}); + + resolve = + resolverImplClass.getMethod("resolve", + new Class[] {String.class, String.class}); + } catch (NoSuchMethodException ex) { + throw new BuildException(ex); + } + + log("Apache resolver library found, xml-commons resolver will be used", + Project.MSG_VERBOSE); + } + + public InputSource resolveEntity(String publicId, + String systemId) { + InputSource result = null; + + processExternalCatalogs(); + + ResourceLocation matchingEntry = findMatchingEntry(publicId); + + if (matchingEntry != null) { + + log("Matching catalog entry found for publicId: '" + + matchingEntry.getPublicId() + "' location: '" + + matchingEntry.getLocation() + "'", + Project.MSG_DEBUG); + + result = filesystemLookup(matchingEntry); + + if (result == null) { + result = classpathLookup(matchingEntry); + } + + if (result == null) { + try { + result = + (InputSource) resolveEntity.invoke(resolverImpl, + new Object[] {publicId, systemId}); + } catch (Exception ex) { + throw new BuildException(ex); + } + } + } else { + // + // We didn't match a ResourceLocation, but since we + // only support PUBLIC and URI entry types internally, + // it is still possible that there is another entry in + // an external catalog that will match. We call + // Apache resolver's resolveEntity method to cover + // this possibility. + // + try { + result = + (InputSource) resolveEntity.invoke(resolverImpl, + new Object[] {publicId, systemId}); + } catch (Exception ex) { + throw new BuildException(ex); + } + } + + return result; + } + + public Source resolve(String href, String base) + throws TransformerException { + + SAXSource result = null; + InputSource source = null; + + processExternalCatalogs(); + + ResourceLocation matchingEntry = findMatchingEntry(href); + + if (matchingEntry != null) { + + log("Matching catalog entry found for uri: '" + + matchingEntry.getPublicId() + "' location: '" + + matchingEntry.getLocation() + "'", + Project.MSG_DEBUG); + + // + // Use the passed in base in preference to the base + // from matchingEntry, which is either null or the + // directory in which the external catalog file from + // which it was obtained is located. We make a copy + // so matchingEntry's original base is untouched. Of + // course, if there is no base, no need to make a + // copy... + // + // This is the standard behavior as per my reading of + // the JAXP and XML Catalog specs. CKS 11/7/2002 + // + ResourceLocation entryCopy = matchingEntry; + if (base != null) { + try { + URL baseURL = new URL(base); + entryCopy = new ResourceLocation(); + entryCopy.setBase(baseURL); + } catch (MalformedURLException ex) { + // ignore + } + } + entryCopy.setPublicId(matchingEntry.getPublicId()); + entryCopy.setLocation(matchingEntry.getLocation()); + + source = filesystemLookup(entryCopy); + + if (source == null) { + source = classpathLookup(entryCopy); + } + + if (source != null) { + result = new SAXSource(source); + } else { + try { + result = + (SAXSource) resolve.invoke(resolverImpl, + new Object[] {href, base}); + } catch (Exception ex) { + throw new BuildException(ex); + } + } + } else { + // + // We didn't match a ResourceLocation, but since we + // only support PUBLIC and URI entry types internally, + // it is still possible that there is another entry in + // an external catalog that will match. We call + // Apache resolver's resolveEntity method to cover + // this possibility. + // + if (base == null) { + try { + base = FILE_UTILS.getFileURL(getProject().getBaseDir()).toString(); + } catch (MalformedURLException x) { + throw new TransformerException(x); + } + } + try { + result = + (SAXSource) resolve.invoke(resolverImpl, + new Object[] {href, base}); + } catch (Exception ex) { + throw new BuildException(ex); + } + } + return result; + } + + /** + * Process each external catalog file specified in a + * <code><catalogpath></code>. It will be + * parsed by the resolver library, and the individual elements + * will be added back to us (that is, the controlling + * XMLCatalog instance) via a callback mechanism. + */ + private void processExternalCatalogs() { + + if (!externalCatalogsProcessed) { + + try { + setXMLCatalog.invoke(resolverImpl, + new Object[] {XMLCatalog.this}); + } catch (Exception ex) { + throw new BuildException(ex); + } + + // Parse each catalog listed in nested <catalogpath> elements + Path catPath = getCatalogPath(); + if (catPath != null) { + log("Using catalogpath '" + getCatalogPath() + "'", + Project.MSG_DEBUG); + String[] catPathList = getCatalogPath().list(); + + for (int i = 0; i < catPathList.length; i++) { + File catFile = new File(catPathList[i]); + log("Parsing " + catFile, Project.MSG_DEBUG); + try { + parseCatalog.invoke(resolverImpl, + new Object[] {catFile.getPath()}); + } catch (Exception ex) { + throw new BuildException(ex); + } + } + } + } + externalCatalogsProcessed = true; + } + } +} //-- XMLCatalog |