FileDocCategorySizeDatePackage
XMLCatalog.javaAPI DocApache Ant 1.7040579Wed Dec 13 06:16:18 GMT 2006org.apache.tools.ant.types

XMLCatalog

public class XMLCatalog extends DataType implements URIResolver, Cloneable, EntityResolver

This data type provides a catalog of resource locations (such as DTDs and XML entities), based on the OASIS "Open Catalog" standard. 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 Java API for XML Processing Specification.

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 plain text format or XML format. If the xml-commons resolver library is not found in the classpath, external catalog files, specified in <catalogpath> paths, will be ignored and a warning will be logged. In this case, however, processing of inline entries will proceed normally.

Currently, only <dtd> and <entity> elements may be specified inline; these correspond to OASIS catalog entry types PUBLIC and URI respectively.

The following is a usage example:

<xmlcatalog>
  <dtd publicId="" location="/path/to/file.jar" />
  <dtd publicId="" location="/path/to/file2.jar" />
  <entity publicId="" location="/path/to/file3.jar" />
  <entity publicId="" location="/path/to/file4.jar" />
  <catalogpath>
    <pathelement location="/etc/sgml/catalog"/>
  </catalogpath>
  <catalogfiles dir="/opt/catalogs/" includes="**\catalog.xml" />
</xmlcatalog>

Tasks wishing to use <xmlcatalog> must provide a method called createXMLCatalog which returns an instance of XMLCatalog. Nested DTD and entity definitions are handled by the XMLCatalog object and must be labeled dtd and entity respectively.

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:

  1. In the local filesystem
  2. In the classpath
  3. Using the Apache xml-commons resolver (if it is available)
  4. In URL-space

See {@link org.apache.tools.ant.taskdefs.optional.XMLValidateTask XMLValidateTask} for an example of a task that has integrated support for XMLCatalogs.

Possible future extension could provide for additional OASIS entry types to be specified inline.

Fields Summary
private static final org.apache.tools.ant.util.FileUtils
FILE_UTILS
helper for some File.toURL connversions
private Vector
elements
Holds dtd/entity objects until needed.
private Path
classpath
Classpath in which to attempt to resolve resources.
private Path
catalogPath
Path listing external catalog files to search when resolving entities
public static final String
APACHE_RESOLVER
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
CATALOG_RESOLVER
Resolver base class
private CatalogResolver
catalogResolver
The instance of the CatalogResolver strategy to use.
Constructors Summary
public XMLCatalog()
Default constructor


        //-- Methods ---------------------------------------------------------------

           
      
        setChecked(false);
    
Methods Summary
public voidaddConfiguredXMLCatalog(org.apache.tools.ant.types.XMLCatalog catalog)
Loads a nested <xmlcatalog> into our definition. Not allowed if this catalog is itself a reference to another catalog -- that is, a catalog cannot both refer to another and contain elements or other attributes.

param
catalog Nested XMLCatalog

        if (isReference()) {
            throw noChildrenAllowed();
        }

        // Add all nested elements to our catalog
        Vector newElements = catalog.getElements();
        Vector ourElements = getElements();
        Enumeration e = newElements.elements();
        while (e.hasMoreElements()) {
            ourElements.addElement(e.nextElement());
        }

        // 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);
    
public voidaddDTD(ResourceLocation dtd)
Creates the nested <dtd> element. Not allowed if this catalog is itself a reference to another catalog -- that is, a catalog cannot both refer to another and 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.

        if (isReference()) {
            throw noChildrenAllowed();
        }

        getElements().addElement(dtd);
        setChecked(false);
    
public voidaddEntity(ResourceLocation entity)
Creates the nested <entity> element. Not allowed if this catalog is itself a reference to another catalog -- that is, a catalog cannot both refer to another and 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.

        addDTD(entity);
    
private org.xml.sax.InputSourceclasspathLookup(ResourceLocation matchingEntry)
Utility method to lookup a ResourceLocation in the classpath.

return
An InputSource for reading the resource, or null if the resource does not exist in the classpath or is not readable.


        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;
    
public PathcreateCatalogPath()
Creates a nested <catalogpath> element. Not allowed if this catalog is itself a reference to another catalog -- that is, a catalog cannot both refer to another and 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.

        if (isReference()) {
            throw noChildrenAllowed();
        }
        if (this.catalogPath == null) {
            this.catalogPath = new Path(getProject());
        }
        setChecked(false);
        return this.catalogPath.createPath();
    
public PathcreateClasspath()
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 and contain elements or other attributes.

return
a Path instance to be configured.

        if (isReference()) {
            throw noChildrenAllowed();
        }
        if (this.classpath == null) {
            this.classpath = new Path(getProject());
        }
        setChecked(false);
        return this.classpath.createPath();
    
private org.xml.sax.InputSourcefilesystemLookup(ResourceLocation matchingEntry)
Utility method to lookup a ResourceLocation in the filesystem.

return
An InputSource for reading the file, or null if the file does not exist or is not readable.


        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;
    
private ResourceLocationfindMatchingEntry(java.lang.String publicId)
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.

        Enumeration e = getElements().elements();
        ResourceLocation element = null;
        while (e.hasMoreElements()) {
            Object o = e.nextElement();
            if (o instanceof ResourceLocation) {
                element = (ResourceLocation) o;
                if (element.getPublicId().equals(publicId)) {
                    return element;
                }
            }
        }
        return null;
    
public PathgetCatalogPath()
Returns the catalog path in which to attempt to resolve DTDs.

return
the catalog path

        return getRef().catalogPath;
    
private org.apache.tools.ant.types.XMLCatalog$CatalogResolvergetCatalogResolver()
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.

This is an application of the Gang of Four Strategy Pattern combined with Template Method.


                                                                          
       

        if (catalogResolver == null) {

            AntClassLoader loader = null;

            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;
    
private PathgetClasspath()
Returns the classpath in which to attempt to resolve resources.

return
the classpath

        return getRef().classpath;
    
private java.util.VectorgetElements()
Returns the elements of the catalog - ResourceLocation objects.

return
the elements of the catalog - ResourceLocation objects

        return getRef().elements;
    
private org.apache.tools.ant.types.XMLCataloggetRef()

since
Ant 1.6

        if (!isReference()) {
            return this;
        }
        return (XMLCatalog) getCheckedRef(XMLCatalog.class, "xmlcatalog");
    
private java.lang.StringremoveFragment(java.lang.String uri)
Utility method to remove trailing fragment from a URI. For example, http://java.sun.com/index.html#chapter1 would return http://java.sun.com/index.html.

param
uri The URI to process. It may or may not contain a fragment.
return
The URI sans fragment.

        String result = uri;
        int hashPos = uri.indexOf("#");
        if (hashPos >= 0) {
            result = uri.substring(0, hashPos);
        }
        return result;
    
public javax.xml.transform.Sourceresolve(java.lang.String href, java.lang.String base)
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


        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;
    
public org.xml.sax.InputSourceresolveEntity(java.lang.String publicId, java.lang.String systemId)
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


        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;
    
public voidsetCatalogPathRef(Reference r)
Allows catalogpath reference. Not allowed if this catalog is itself a reference to another catalog -- that is, a catalog cannot both refer to another and contain elements or other attributes.

param
r an Ant reference containing a classpath to be used as the catalog path.

        if (isReference()) {
            throw tooManyAttributes();
        }
        createCatalogPath().setRefid(r);
        setChecked(false);
    
public voidsetClasspath(Path classpath)
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 and contain elements or other attributes.

param
classpath the classpath to use to look up entities.

        if (isReference()) {
            throw tooManyAttributes();
        }
        if (this.classpath == null) {
            this.classpath = classpath;
        } else {
            this.classpath.append(classpath);
        }
        setChecked(false);
    
public voidsetClasspathRef(Reference r)
Allows classpath reference. Not allowed if this catalog is itself a reference to another catalog -- that is, a catalog cannot both refer to another and contain elements or other attributes.

param
r an Ant reference containing a classpath.

        if (isReference()) {
            throw tooManyAttributes();
        }
        createClasspath().setRefid(r);
        setChecked(false);
    
private voidsetEntityResolver(javax.xml.transform.sax.SAXSource source)

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:

"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"

...quoted from page 118 of the Java API for XML Processing 1.1 specification


        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);
    
public voidsetRefid(Reference r)
Makes this instance in effect a reference to another XMLCatalog instance.

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 and contain elements or attributes.

param
r the reference to which this catalog instance is associated
exception
BuildException if this instance already has been configured.

        if (!elements.isEmpty()) {
            throw tooManyAttributes();
        }
        super.setRefid(r);
    
private org.xml.sax.InputSourceurlLookup(ResourceLocation matchingEntry)
Utility method to lookup a ResourceLocation in URL-space.

return
An InputSource for reading the resource, or null if the resource does not identify a valid URL or is not readable.


        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 = 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;