Open Source Repository

Home /commons-configuration/commons-configuration-1.7 | Repository Home


org/apache/commons/configuration/resolver/CatalogResolver.java
/*
 * 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.commons.configuration.resolver;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.apache.xml.resolver.CatalogException;
import org.apache.xml.resolver.readers.CatalogReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.configuration.FileSystem;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.ConfigurationUtils;
import org.apache.commons.lang.text.StrSubstitutor;

import java.io.InputStream;
import java.io.IOException;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.net.URL;
import java.util.Vector;

/**
 * Thin wrapper around xml commons CatalogResolver to allow list of catalogs
 * to be provided.
 @author <a
 * href="http://commons.apache.org/configuration/team-list.html">Commons
 * Configuration team</a>
 @since 1.7
 @version $Id: CatalogResolver.java 952631 2010-06-08 12:09:29Z sebb $
 */
public class CatalogResolver implements EntityResolver
{
    /**
     * Debug everything.
     */
    private static final int DEBUG_ALL = 9;

    /**
     * Normal debug setting.
     */
    private static final int DEBUG_NORMAL = 4;

    /**
     * Debug nothing.
     */
    private static final int DEBUG_NONE = 0;

    /**
     * The CatalogManager
     */
    protected CatalogManager manager = new CatalogManager();

    /**
     * The FileSystem in use.
     */
    protected FileSystem fs = FileSystem.getDefaultFileSystem();

    /**
     * The CatalogResolver
     */
    private org.apache.xml.resolver.tools.CatalogResolver resolver;

    /**
     * Stores the logger.
     */
    private Log log;

    /**
     * Constructs the CatalogResolver
     */
    public CatalogResolver()
    {
        manager.setIgnoreMissingProperties(true);
        manager.setUseStaticCatalog(false);
        manager.setFileSystem(fs);
        setLogger(null);
    }

    /**
     * Set the list of catalog file names
     *
     @param catalogs The delimited list of catalog files.
     */
    public void setCatalogFiles(String catalogs)
    {
        manager.setCatalogFiles(catalogs);
    }

    /**
     * Set the FileSystem.
     @param fileSystem The FileSystem.
     */
    public void setFileSystem(FileSystem fileSystem)
    {
        this.fs = fileSystem;
        manager.setFileSystem(fileSystem);
    }

    /**
     * Set the base path.
     @param baseDir The base path String.
     */
    public void setBaseDir(String baseDir)
    {
        manager.setBaseDir(baseDir);
    }

    /**
     * Set the StrSubstitutor.
     @param substitutor The StrSubstitutor.
     */
    public void setSubstitutor(StrSubstitutor substitutor)
    {
        manager.setSubstitutor(substitutor);
    }

    /**
     * Enables debug logging of xml-commons Catalog processing.
     @param debug True if debugging should be enabled, false otherwise.
     */
    public void setDebug(boolean debug)
    {
        if (debug)
        {
            manager.setVerbosity(DEBUG_ALL);
        }
        else
        {
            manager.setVerbosity(DEBUG_NONE);
        }
    }

    /**
     * Implements the <code>resolveEntity</code> method
     * for the SAX interface.
     <p/>
     <p>Presented with an optional public identifier and a system
     * identifier, this function attempts to locate a mapping in the
     * catalogs.</p>
     <p/>
     <p>If such a mapping is found, the resolver attempts to open
     * the mapped value as an InputSource and return it. Exceptions are
     * ignored and null is returned if the mapped value cannot be opened
     * as an input source.</p>
     <p/>
     <p>If no mapping is found (or an error occurs attempting to open
     * the mapped value as an input source), null is returned and the system
     * will use the specified system identifier as if no entityResolver
     * was specified.</p>
     *
     @param publicId The public identifier for the entity in question.
     *                 This may be null.
     @param systemId The system identifier for the entity in question.
     *                 XML requires a system identifier on all external entities, so this
     *                 value is always specified.
     @return An InputSource for the mapped identifier, or null.
     @throws SAXException if an error occurs.
     */
    public InputSource resolveEntity(String publicId, String systemId)
            throws SAXException
    {
        String resolved = getResolver().getResolvedEntity(publicId, systemId);

        if (resolved != null)
        {
            String badFilePrefix = "file://";
            String correctFilePrefix = "file:///";

            // Java 5 has a bug when constructing file URLS
            if (resolved.startsWith(badFilePrefix&& !resolved.startsWith(correctFilePrefix))
            {
                resolved = correctFilePrefix + resolved.substring(badFilePrefix.length());
            }

            try
            {
                InputStream is = fs.getInputStream(null, resolved);
                InputSource iSource = new InputSource(resolved);
                iSource.setPublicId(publicId);
                iSource.setByteStream(is);
                return iSource;
            }
            catch (Exception e)
            {
                log.warn("Failed to create InputSource for " + resolved + " ("
                                + e.toString() ")");
                return null;
            }
        }

        return null;
    }

    /**
     * Returns the logger used by this configuration object.
     *
     @return the logger
     */
    public Log getLogger()
    {
        return log;
    }

    /**
     * Allows to set the logger to be used by this configuration object. This
     * method makes it possible for clients to exactly control logging behavior.
     * Per default a logger is set that will ignore all log messages. Derived
     * classes that want to enable logging should call this method during their
     * initialization with the logger to be used.
     *
     @param log the new logger
     */
    public void setLogger(Log log)
    {
        this.log = (log != null? log : LogFactory.getLog(CatalogResolver.class);
    }

    private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver()
    {
        if (resolver == null)
        {
            resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager);
        }
        return resolver;
    }

    /**
     * Extend the CatalogManager to make the FileSystem and base directory accessible.
     */
    public static class CatalogManager extends org.apache.xml.resolver.CatalogManager
    {
        /** The static catalog used by this manager. */
        private static org.apache.xml.resolver.Catalog staticCatalog;

        /** The FileSystem */
        private FileSystem fs;

        /** The base directory */
        private String baseDir = System.getProperty("user.dir");

        /** The String Substitutor */
        private StrSubstitutor substitutor;

        /**
         * Set the FileSystem
         @param fileSystem The FileSystem in use.
         */
        public void setFileSystem(FileSystem fileSystem)
        {
            this.fs = fileSystem;
        }

        /**
         * Retrieve the FileSystem.
         @return The FileSystem.
         */
        public FileSystem getFileSystem()
        {
            return this.fs;
        }

        /**
         * Set the base directory.
         @param baseDir The base directory.
         */
        public void setBaseDir(String baseDir)
        {
            if (baseDir != null)
            {
                this.baseDir = baseDir;
            }
        }

        /**
         * Return the base directory.
         @return The base directory.
         */
        public String getBaseDir()
        {
            return this.baseDir;
        }

        public void setSubstitutor(StrSubstitutor substitutor)
        {
            this.substitutor = substitutor;
        }

        public StrSubstitutor getStrSubstitutor()
        {
            return this.substitutor;
        }


        /**
         * Get a new catalog instance. This method is only overridden because xml-resolver
         * might be in a parent ClassLoader and will be incapable of loading our Catalog
         * implementation.
         *
         * This method always returns a new instance of the underlying catalog class.
         @return the Catalog.
         */
        public org.apache.xml.resolver.Catalog getPrivateCatalog()
        {
            org.apache.xml.resolver.Catalog catalog = staticCatalog;

            if (catalog == null || !getUseStaticCatalog())
            {
                try
                {
                    catalog = new Catalog();
                    catalog.setCatalogManager(this);
                    catalog.setupReaders();
                    catalog.loadSystemCatalogs();
                }
                catch (Exception ex)
                {
                    ex.printStackTrace();
                }

                if (getUseStaticCatalog())
                {
                    staticCatalog = catalog;
                }
            }

            return catalog;
        }

        /**
         * Get a catalog instance.
         *
         * If this manager uses static catalogs, the same static catalog will
         * always be returned. Otherwise a new catalog will be returned.
         @return The Catalog.
         */
        public org.apache.xml.resolver.Catalog getCatalog()
        {
            return getPrivateCatalog();
        }
    }

    /**
     * Overrides the Catalog implementation to use the underlying FileSystem.
     */
    public static class Catalog extends org.apache.xml.resolver.Catalog
    {
        /** The FileSystem */
        private FileSystem fs;

        /** FileNameMap to determine the mime type */
        private FileNameMap fileNameMap = URLConnection.getFileNameMap();

        /**
         * Load the catalogs.
         @throws IOException if an error occurs.
         */
        public void loadSystemCatalogs() throws IOException
        {
            fs = ((CatalogManagercatalogManager).getFileSystem();
            String base = ((CatalogManagercatalogManager).getBaseDir();

            Vector catalogs = catalogManager.getCatalogFiles();
            if (catalogs != null)
            {
                for (int count = 0; count < catalogs.size(); count++)
                {
                    String fileName = (Stringcatalogs.elementAt(count);

                    URL url = null;
                    InputStream is = null;

                    try
                    {
                        url = ConfigurationUtils.locate(fs, base, fileName);
                        if (url != null)
                        {
                            is = fs.getInputStream(url);
                        }
                    }
                    catch (ConfigurationException ce)
                    {
                        String name = (url == null? fileName : url.toString();
                        // Ignore the exception.
                        catalogManager.debug.message(DEBUG_ALL,
                            "Unable to get input stream for " + name + ". " + ce.getMessage());
                    }
                    if (is != null)
                    {
                        String mimeType = fileNameMap.getContentTypeFor(fileName);
                        try
                        {
                            if (mimeType != null)
                            {
                                parseCatalog(mimeType, is);
                                continue;
                            }
                        }
                        catch (Exception ex)
                        {
                            // Ignore the exception.
                            catalogManager.debug.message(DEBUG_ALL,
                                "Exception caught parsing input stream for " + fileName + ". "
                                + ex.getMessage());
                        }
                        finally
                        {
                            is.close();
                        }
                    }
                    parseCatalog(base, fileName);
                }
            }

        }

        /**
         * Parse the specified catalog file.
         @param baseDir The base directory, if not included in the file name.
         @param fileName The catalog file. May be a full URI String.
         @throws IOException If an error occurs.
         */
        public void parseCatalog(String baseDir, String fileNamethrows IOException
        {
            base = ConfigurationUtils.locate(fs, baseDir, fileName);
            catalogCwd = base;
            default_override = catalogManager.getPreferPublic();
            catalogManager.debug.message(DEBUG_NORMAL, "Parse catalog: " + fileName);

            boolean parsed = false;

            for (int count = 0; !parsed && count < readerArr.size(); count++)
            {
                CatalogReader reader = (CatalogReaderreaderArr.get(count);
                InputStream inStream;

                try
                {
                    inStream = fs.getInputStream(base);
                }
                catch (Exception ex)
                {
                    catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base
                        + ex.getMessage());
                    break;
                }

                try
                {
                    reader.readCatalog(this, inStream);
                    parsed = true;
                }
                catch (CatalogException ce)
                {
                    catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName
                            + ce.getMessage());
                    if (ce.getExceptionType() == CatalogException.PARSE_FAILED)
                    {
                        break;
                    }
                    else
                    {
                        // try again!
                        continue;
                    }
                }
                finally
                {
                    try
                    {
                        inStream.close();
                    }
                    catch (IOException ioe)
                    {
                        // Ignore the exception.
                        inStream = null;
                    }
                }
            }

            if (parsed)
            {
                parsePendingCatalogs();
            }
        }

        /**
         * Perform character normalization on a URI reference.
         *
         @param uriref The URI reference
         @return The normalized URI reference.
         */
        protected String normalizeURI(String uriref)
        {
            StrSubstitutor substitutor = ((CatalogManagercatalogManager).getStrSubstitutor();
            String resolved = substitutor != null ? substitutor.replace(uriref: uriref;
            return super.normalizeURI(resolved);
        }
    }
}