/* * 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.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; 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 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. */ @Override 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 */ @Override 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 */ @Override 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; 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; } @Override 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; // 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) { return getElements().stream() .filter(e -> e.getPublicId().equals(publicId)).findFirst() .orElse(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; // // 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"); } } 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); } } InputSource source = null; if (url != null && "file".equals(url.getProtocol())) { 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(Files.newInputStream(resFile.toPath())); 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; Path cp = classpath; if (cp != null) { cp = classpath.concatSystemClasspath("ignore"); } else { cp = (new Path(getProject())).concatSystemClasspath("last"); } AntClassLoader 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; // // 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"); } } URL url; try { url = new URL(baseURL, uri); } catch (MalformedURLException ex) { url = null; } InputSource source = null; 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 { } /** * 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); } @Override 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; } @Override 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); } @Override public InputSource resolveEntity(String publicId, String systemId) { processExternalCatalogs(); ResourceLocation matchingEntry = findMatchingEntry(publicId); InputSource result; 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; } @Override public Source resolve(String href, String base) throws TransformerException { SAXSource result; InputSource source; 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