Hello:
After carefully studying both the Ant <xmlcatalog> datatype and the xml-commons resolver library, I have designed and implemented an integration that I believe will fit the bill. I submitted my code to ant-dev last night, and hopefully it will make it into the core.
Here is what I did:
The original <xmlcatalog> datatype provides the ability to specify elements that roughly correspond to PUBLIC and URI catalog entries. It was implemented without any external code dependencies, specifically there are no calls to the xml-commons resolver.
I have come to realize that, for the vast majority of Ant users, that will be enough. On the other hand, a few people (like me :-) need the full capabilities of OASIS catalogs.
So I created my own subclasses of ResolverCatalog and Catalog and I made XMLCatalog.java call out to ResolverCatalog using reflection.
If the classloader _can_ instantiate the object, I know resolver.jar is in the classpath.
If the classloader _can not_ instantiate the object, I know resolver.jar is NOT in the classpath.
If resolver.jar is available, <xmlcatalog> will process <catalogfiles> entries that identify external OASIS catalog files. If it is not, any <catalogfiles> entries will be ignored (a build warning will be logged).
I have attached the java sourcecode that makes up the bulk of the integration
for your delectation. It is thoroughly commented and fairly well factored, so shouldn't be difficult to read through...(famous last words...:-)
Comments welcomed.
Regards,
--Craeg
PS I noticed that the word "element" and "entry" are used interchangeably in the OASIS XML Catalog standard. Is this intentional? Perhaps choosing one or the other would be better...
/* * The Apache Software License, Version 1.1 * * Copyright (c) 2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Ant", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */
package org.apache.tools.ant.types; import java.lang.reflect.Method; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.io.IOException; import java.net.URL; import java.net.MalformedURLException; import java.util.Enumeration; import java.util.Vector; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.AntClassLoader; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.apache.tools.ant.util.FileUtils; import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; import javax.xml.transform.sax.SAXSource; /** * <p>This data type provides a catalog of Resource locations, 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 [EMAIL PROTECTED] org.xml.sax.EntityResolver EntityResolver} and [EMAIL PROTECTED] * 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><catalogfiles></code> filesets, 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. By contrast, external catalog files * may use any of the entry types defined in the OASIS * specification.</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" /gt;<br> * <entity publicId="" location="/path/to/file3.jar" /><br> * <entity publicId="" location="/path/to/file4.jar" /><br> * <catalogfiles dir="${basedir}" includes="**\catalog" /><br> * <catalogfiles dir="/opt/catalogs/" includes="**\catalog.xml" /><br> * </xmlcatalog><br> * </code> * * <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 [EMAIL PROTECTED] * 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> * * @author dIon Gillard * @author Erik Hatcher * @author <a href="mailto:[EMAIL PROTECTED]">Craeg Strong</a> * @version $Id: XMLCatalog.java,v 1.9 2002/04/20 16:10:04 ehatcher Exp $ */ public class XMLCatalog extends DataType implements Cloneable, EntityResolver, URIResolver { //-- Fields ---------------------------------------------------------------- /** File utilities instance. */ private FileUtils fileUtils = FileUtils.newFileUtils(); /** Holds dtd/entity objects and catalog filesets until needed. */ private Vector elements = new Vector(); /** * Classpath in which to attempt to resolve resources. */ private Path classpath; /** * 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"; //-- Methods --------------------------------------------------------------- /** * Returns the elements of the catalog - ResolverLocation and * FileSet objects. * * @return the elements of the catalog - ResolverLocation and * FileSet objects */ private Vector getElements() { return elements; } /** * Returns the classpath in which to attempt to resolve resources. * * @return the classpath */ private Path getClasspath() { return classpath; } /** * Set the list of ResourceLocation objects and FileSets in the * catalog. 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 aVector the new list of ResourceLocations and FileSets * to use in the catalog. */ private void setElements(Vector aVector) { if (isReference()) { throw noChildrenAllowed(); } elements = aVector; } /** * 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. */ public Path createClasspath() { if (isReference()) { throw noChildrenAllowed(); } if (this.classpath == null) { this.classpath = new Path(getProject()); } 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. */ public void setClasspath(Path classpath) { if (isReference()) { throw tooManyAttributes(); } if (this.classpath == null) { this.classpath = classpath; } else { this.classpath.append(classpath); } } /** * 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. */ public void setClasspathRef(Reference r) { if (isReference()) { throw tooManyAttributes(); } createClasspath().setRefid(r); } /** * Creates the nested <code><catalogfiles></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 fs the fileset of external catalogs. * @exception BuildException if this is a reference and no nested * elements are allowed. */ public void addCatalogfiles(FileSet fs) throws BuildException { if (isReference()) { throw noChildrenAllowed(); } getElements().addElement(fs); } /** * 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(DTDLocation dtd) throws BuildException { if (isReference()) { throw noChildrenAllowed(); } getElements().addElement(dtd); } /** * 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(EntityLocation entity) throws BuildException { if (isReference()) { throw noChildrenAllowed(); } getElements().addElement(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 Vector newElements = catalog.getElements(); Vector ourElements = getElements(); Enumeration enum = newElements.elements(); while (enum.hasMoreElements()) { ourElements.addElement(enum.nextElement()); } // Append the classpath of the nested catalog Path nestedClasspath = catalog.getClasspath(); createClasspath().append(nestedClasspath); } /** * 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 has already been configured. */ public void setRefid(Reference r) throws BuildException { if (!elements.isEmpty()) { throw tooManyAttributes(); } // change this to get the objects from the other reference Object o = r.getReferencedObject(getProject()); // we only support references to other XCatalogs if (o instanceof XMLCatalog) { // set all elements from referenced catalog to this one XMLCatalog catalog = (XMLCatalog) o; setElements(catalog.getElements()); } else { String msg = r.getRefId() + " does not refer to an XMLCatalog"; throw new BuildException(msg); } super.setRefid(r); } /** * Implements the EntityResolver.resolveEntity() interface method. * * @see org.xml.sax.EntityResolver#resolveEntity */ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 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. * * @see javax.xml.transform.URIResolver#resolve */ public Source resolve(String href, String base) throws TransformerException { SAXSource source = null; InputSource inputSource = 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); } else { setEntityResolver(source); } return source; } /** * <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) { Enumeration enum = getElements().elements(); ResourceLocation element = null; while (enum.hasMoreElements()) { Object o = enum.nextElement(); if (o instanceof ResourceLocation) { element = (ResourceLocation)o; 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; String fragment = null; int hashPos = uri.indexOf("#"); if (hashPos >= 0) { result = uri.substring(0, hashPos); fragment = uri.substring(hashPos+1); } 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(); File basedir = null; // // The ResourceLocation may specify a relative path for its // location attribute. This is resolved using the appropriate // base. // if (matchingEntry.getBase() != null) { basedir = new File(matchingEntry.getBase()); } else { basedir = project.getBaseDir(); } File resFile = null; InputSource source = null; if (basedir == null) { resFile = new File(uri); } else { resFile = fileUtils.resolveFile(basedir, uri); } if (resFile.exists() && resFile.canRead()) { try { source = new InputSource(new FileInputStream(resFile)); URL resFileURL = fileUtils.getFileURL(resFile); String sysid = resFileURL.toExternalForm(); source.setSystemId(sysid); log("catalog entry matched a readable file: '" + sysid + "'", Project.MSG_DEBUG); } catch(FileNotFoundException ex) { // ignore } catch(MalformedURLException ex) { // ignore } 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; if (classpath != null) { loader = new AntClassLoader(project, classpath); } else { loader = new AntClassLoader(project, Path.systemClasspath); } // // 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(String uri, String base) { InputSource source = null; URL url = null; try { if (base == null) { url = new URL(uri); } else { URL baseURL = new URL(base); url = (uri.length() == 0 ? baseURL : new URL(baseURL, uri)); } } catch (MalformedURLException ex) { // ignore } if (url != null) { try { InputStream is = url.openStream(); 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; } /** * The instance of the CatalogResolver strategy to use. */ private static CatalogResolver catalogResolver = null; /** * <p>Factory method for creating the appropriate CatalogResolver * strategy implementation. 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; loader = new AntClassLoader(project, Path.systemClasspath); try { Class clazz = loader.forceLoadSystemClass(APACHE_RESOLVER); Object obj = clazz.newInstance(); // // Success! The xml-commons resolver library is // available, so use it. // catalogResolver = new ApacheResolver(clazz, obj); } catch (Throwable ex) { // // The xml-commons resolver library is not // available, so we can't use it. // catalogResolver = new InternalResolver(); // // If any <catalogfiles> are specified, warn that they // will be ignored. // Enumeration enum = getElements().elements(); while (enum.hasMoreElements()) { Object o = enum.nextElement(); if (o instanceof FileSet) { log("Warning: External catalogfiles will be ignored", Project.MSG_WARN); break; } } } } return catalogResolver; } /** * Interface implemented by both the InternalResolver strategy and * the ApacheResolver strategy. */ private interface CatalogResolver extends URIResolver, EntityResolver { public InputSource resolveEntity(String publicId, String systemId); public 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_INFO); } 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.getLocation(), null); } } 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); source = filesystemLookup(matchingEntry); if (source == null) { source = classpathLookup(matchingEntry); } if (source == null) { source = urlLookup(matchingEntry.getLocation(), base); } if (source != null) { result = new SAXSource(source); } } return result; } } /** * The ApacheResolver strategy is used if the Apache resolver * library (Norm Walsh's library from xml-commons) is available in * the classpath. The ApacheResolver is a essentially a superset * of the InternalResolver. * */ private class ApacheResolver 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 ApacheResolver(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_INFO); } 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, 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); source = filesystemLookup(matchingEntry); if (source == null) { source = classpathLookup(matchingEntry); } 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, 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 = (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><catalogfiles></code> FileSet. 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 == false) { try { setXMLCatalog.invoke(resolverImpl, new Object[] { XMLCatalog.this }); } catch (Exception ex) { throw new BuildException(ex); } Enumeration enum = getElements().elements(); while (enum.hasMoreElements()) { Object o = enum.nextElement(); if (o instanceof FileSet) { FileSet fs = (FileSet)o; DirectoryScanner ds = fs.getDirectoryScanner(getProject()); String[] files = ds.getIncludedFiles(); for (int i = 0; i < files.length; i++) { File catFile = getProject().resolveFile(files[i]); try { parseCatalog.invoke(resolverImpl, new Object[] { catFile.getPath() }); } catch (Exception ex) { throw new BuildException(ex); } } } } } externalCatalogsProcessed = true; } } } //-- XMLCatalog
/* * The Apache Software License, Version 1.1 * * Copyright (c) 2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Ant", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.tools.ant.types.resolver; import org.apache.xml.resolver.Catalog; import org.apache.xml.resolver.CatalogEntry; import org.apache.xml.resolver.helpers.Debug; import org.apache.xml.resolver.helpers.PublicId; /** * This class extends the Catalog class provided by Norman Walsh's * resolver library in xml-commons in order to add classpath entity * and URI resolution. Since XMLCatalog already does classpath * resolution, we simply add all CatalogEntry instances back to the * controlling XMLCatalog instance. This is done via a callback * mechanism. ApacheCatalog is <em>only</em> used for external * catalog files. Inline entries (currently <code><dtd></code> * and <code><entity></code>) are not added to ApacheCatalog. * See XMLCatalog.java for the details of the entity and URI * resolution algorithms. * * @see org.apache.tools.ant.types.XMLCatalog.CatalogResolver * @author <a href="mailto:[EMAIL PROTECTED]">Craeg Strong</a> * @version $Id: $ */ public class ApacheCatalog extends Catalog { /** The resolver object to callback. */ private ApacheCatalogResolver resolver = null; /** * <p>Create a new ApacheCatalog instance.</p> * * <p>This method overrides the superclass method of the same name * in order to set the resolver object for callbacks. The reason * we have to do this is that internally Catalog creates a new * instance of itself for each external catalog file processed. * That is, if two external catalog files are processed, there * will be a total of two ApacheCatalog instances, and so on.</p> */ protected Catalog newCatalog() { ApacheCatalog cat = (ApacheCatalog)super.newCatalog(); cat.setResolver(resolver); return cat; } /** Set the resolver object to callback. */ public void setResolver(ApacheCatalogResolver resolver) { this.resolver = resolver; } /** * <p>This method overrides the superclass method of the same name * in order to add catalog entries back to the controlling * XMLCatalog instance. In this way, we can add classpath lookup * for these entries.</p> * * <p>When we add an external catalog file, the entries inside it * get parsed by this method. Therefore, we override it to add * each of them back to the controlling XMLCatalog instance. This * is done by performing a callback to the ApacheCatalogResolver, * which in turn calls the XMLCatalog.</p> * * <p>XMLCatalog currently only understands <code>PUBLIC</code> * and <code>URI</code> entry types, so we ignore the other types.</p> * * @param entry The CatalogEntry to process. */ public void addEntry(CatalogEntry entry) { int type = entry.getEntryType(); if (type == PUBLIC) { String publicid = PublicId.normalize(entry.getEntryArg(0)); String systemid = normalizeURI(entry.getEntryArg(1)); if (resolver == null) { Debug.message(1, "Internal Error: null ApacheCatalogResolver"); } else { resolver.addPublicEntry(publicid, systemid, base.toExternalForm()); } } else if (type == URI) { String uri = normalizeURI(entry.getEntryArg(0)); String altURI = normalizeURI(entry.getEntryArg(1)); if (resolver == null) { Debug.message(1, "Internal Error: null ApacheCatalogResolver"); } else { resolver.addURIEntry(uri, altURI, base.toExternalForm()); } } super.addEntry(entry); } } //- ApacheCatalog
/* * The Apache Software License, Version 1.1 * * Copyright (c) 2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Ant", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.tools.ant.types.resolver; import java.io.IOException; import java.net.MalformedURLException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.types.XMLCatalog; import org.apache.tools.ant.types.DTDLocation; import org.apache.tools.ant.types.EntityLocation; import org.apache.xml.resolver.Catalog; import org.apache.xml.resolver.CatalogManager; import org.apache.xml.resolver.tools.CatalogResolver; /** * <p>This class extends the CatalogResolver class provided by Norman * Walsh's resolver library in xml-commons. It provides the bridge * between the Ant XMLCatalog datatype and the xml-commons Catalog * class. XMLCatalog calls methods in this class using Reflection in * order to avoid requiring the xml-commons resolver library in the * path.</p> * * <p>The [EMAIL PROTECTED] org.apache.tools.ant.types.resolver.ApacheCatalog * ApacheCatalog} class is used to parse external catalog files, which * can be in 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>.</p> * * <p>For each entry found in an external catalog file, if any, an * instance of [EMAIL PROTECTED] org.apache.tools.ant.types.ResourceLocation * ResourceLocation} is created and added to the controlling * XMLCatalog datatype. In this way, these entries will be included * in XMLCatalog's lookup algorithm. See XMLCatalog.java for more * details.</p> * * @see org.apache.tools.ant.types.XMLCatalog.CatalogResolver * @see org.apache.xml.resolver.CatalogManager * @author <a href="mailto:[EMAIL PROTECTED]">Craeg Strong</a> * @version $Id: $ */ public class ApacheCatalogResolver extends CatalogResolver { /** The XMLCatalog object to callback. */ private XMLCatalog xmlCatalog = null; static { // // If you don't do this, you get all sorts of annoying // warnings about a missing properties file. However, it // seems to work just fine with default values. Ultimately, // we should probably include a "CatalogManager.properties" // file in the ant jarfile with some default property // settings. See CatalogManager.java for more details. // CatalogManager.ignoreMissingProperties(true); // // Make sure CatalogResolver instantiates ApacheCatalog, // rather than a plain Catalog // System.setProperty("xml.catalog.className", ApacheCatalog.class.getName()); // debug // System.setProperty("xml.catalog.verbosity", "4"); } /** Set the XMLCatalog object to callback. */ public void setXMLCatalog(XMLCatalog xmlCatalog) { this.xmlCatalog = xmlCatalog; } /** * XMLCatalog calls this to add an external catalog file for each * file within a <code><catalogfiles></code> fileset. */ public void parseCatalog(String file) { ApacheCatalog catalog = (ApacheCatalog)getCatalog(); // Pass in reference to ourselves so we can be called back. catalog.setResolver(this); try { catalog.parseCatalog(file); } catch(MalformedURLException ex) { throw new BuildException(ex); } catch(IOException ex) { throw new BuildException(ex); } } /** * <p>Add a PUBLIC catalog entry to the controlling XMLCatalog instance. * ApacheCatalog calls this for each PUBLIC entry found in an external * catalog file.</p> * * @param publicid The public ID of the resource * @param systemid The system ID (aka location) of the resource * @param base The base URL of the resource. If the systemid * specifies a relative URL/pathname, it is resolved using the * base. The default base for an external catalog file is the * directory in which the catalog is located. * */ public void addPublicEntry(String publicid, String systemid, String base) { DTDLocation dtd = new DTDLocation(); dtd.setBase(base); dtd.setPublicId(publicid); dtd.setLocation(systemid); xmlCatalog.addDTD(dtd); } /** * <p>Add a URI catalog entry to the controlling XMLCatalog instance. * ApacheCatalog calls this for each URI entry found in an external * catalog file.</p> * * @param URI The URI of the resource * @param altURI The URI to which the resource should be mapped * (aka the location) * @param base The base URL of the resource. If the altURI * specifies a relative URL/pathname, it is resolved using the * base. The default base for an external catalog file is the * directory in which the catalog is located. * */ public void addURIEntry(String uri, String altURI, String base) { EntityLocation entity = new EntityLocation(); entity.setBase(base); entity.setPublicId(uri); entity.setLocation(altURI); xmlCatalog.addEntity(entity); } } //-- ApacheCatalogResolver
