    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *     http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.configuration.resolver;
    018
    019    import java.io.IOException;
    020    import java.io.InputStream;
    021    import java.net.FileNameMap;
    022    import java.net.URL;
    023    import java.net.URLConnection;
    024    import java.util.Vector;
    025
    026    import org.apache.commons.configuration.ConfigurationException;
    027    import org.apache.commons.configuration.ConfigurationUtils;
    028    import org.apache.commons.configuration.FileSystem;
    029    import org.apache.commons.lang.text.StrSubstitutor;
    030    import org.apache.commons.logging.Log;
    031    import org.apache.commons.logging.LogFactory;
    032    import org.apache.xml.resolver.CatalogException;
    033    import org.apache.xml.resolver.readers.CatalogReader;
    034    import org.xml.sax.EntityResolver;
    035    import org.xml.sax.InputSource;
    036    import org.xml.sax.SAXException;
    037
    038    /**
    039     * Thin wrapper around xml commons CatalogResolver to allow list of catalogs
    040     * to be provided.
    041     * @author <a
    042     * href="http://commons.apache.org/configuration/team-list.html">Commons
    043     * Configuration team</a>
    044     * @since 1.7
    045     * @version $Id: CatalogResolver.java 1206764 2011-11-27 16:45:39Z oheger $
    046     */
    047    public class CatalogResolver implements EntityResolver
    048    {
    049        /**
    050         * Debug everything.
    051         */
    052        private static final int DEBUG_ALL = 9;
    053
    054        /**
    055         * Normal debug setting.
    056         */
    057        private static final int DEBUG_NORMAL = 4;
    058
    059        /**
    060         * Debug nothing.
    061         */
    062        private static final int DEBUG_NONE = 0;
    063
    064        /**
    065         * The CatalogManager
    066         */
    067        protected CatalogManager manager = new CatalogManager();
    068
    069        /**
    070         * The FileSystem in use.
    071         */
    072        protected FileSystem fs = FileSystem.getDefaultFileSystem();
    073
    074        /**
    075         * The CatalogResolver
    076         */
    077        private org.apache.xml.resolver.tools.CatalogResolver resolver;
    078
    079        /**
    080         * Stores the logger.
    081         */
    082        private Log log;
    083
    084        /**
    085         * Constructs the CatalogResolver
    086         */
    087        public CatalogResolver()
    088        {
    089            manager.setIgnoreMissingProperties(true);
    090            manager.setUseStaticCatalog(false);
    091            manager.setFileSystem(fs);
    092            setLogger(null);
    093        }
    094
    095        /**
    096         * Set the list of catalog file names
    097         *
    098         * @param catalogs The delimited list of catalog files.
    099         */
    100        public void setCatalogFiles(String catalogs)
    101        {
    102            manager.setCatalogFiles(catalogs);
    103        }
    104
    105        /**
    106         * Set the FileSystem.
    107         * @param fileSystem The FileSystem.
    108         */
    109        public void setFileSystem(FileSystem fileSystem)
    110        {
    111            this.fs = fileSystem;
    112            manager.setFileSystem(fileSystem);
    113        }
    114
    115        /**
    116         * Set the base path.
    117         * @param baseDir The base path String.
    118         */
    119        public void setBaseDir(String baseDir)
    120        {
    121            manager.setBaseDir(baseDir);
    122        }
    123
    124        /**
    125         * Set the StrSubstitutor.
    126         * @param substitutor The StrSubstitutor.
    127         */
    128        public void setSubstitutor(StrSubstitutor substitutor)
    129        {
    130            manager.setSubstitutor(substitutor);
    131        }
    132
    133        /**
    134         * Enables debug logging of xml-commons Catalog processing.
    135         * @param debug True if debugging should be enabled, false otherwise.
    136         */
    137        public void setDebug(boolean debug)
    138        {
    139            if (debug)
    140            {
    141                manager.setVerbosity(DEBUG_ALL);
    142            }
    143            else
    144            {
    145                manager.setVerbosity(DEBUG_NONE);
    146            }
    147        }
    148
    149        /**
    150         * Implements the {@code resolveEntity} method
    151         * for the SAX interface.
    152         * <p/>
    153         * <p>Presented with an optional public identifier and a system
    154         * identifier, this function attempts to locate a mapping in the
    155         * catalogs.</p>
    156         * <p/>
    157         * <p>If such a mapping is found, the resolver attempts to open
    158         * the mapped value as an InputSource and return it. Exceptions are
    159         * ignored and null is returned if the mapped value cannot be opened
    160         * as an input source.</p>
    161         * <p/>
    162         * <p>If no mapping is found (or an error occurs attempting to open
    163         * the mapped value as an input source), null is returned and the system
    164         * will use the specified system identifier as if no entityResolver
    165         * was specified.</p>
    166         *
    167         * @param publicId The public identifier for the entity in question.
    168         *                 This may be null.
    169         * @param systemId The system identifier for the entity in question.
    170         *                 XML requires a system identifier on all external entities, so this
    171         *                 value is always specified.
    172         * @return An InputSource for the mapped identifier, or null.
    173         * @throws SAXException if an error occurs.
    174         */
    175        public InputSource resolveEntity(String publicId, String systemId)
    176                throws SAXException
    177        {
    178            String resolved = getResolver().getResolvedEntity(publicId, systemId);
    179
    180            if (resolved != null)
    181            {
    182                String badFilePrefix = "file://";
    183                String correctFilePrefix = "file:///";
    184
    185                // Java 5 has a bug when constructing file URLS
    186                if (resolved.startsWith(badFilePrefix) && !resolved.startsWith(correctFilePrefix))
    187                {
    188                    resolved = correctFilePrefix + resolved.substring(badFilePrefix.length());
    189                }
    190
    191                try
    192                {
    193                    InputStream is = fs.getInputStream(null, resolved);
    194                    InputSource iSource = new InputSource(resolved);
    195                    iSource.setPublicId(publicId);
    196                    iSource.setByteStream(is);
    197                    return iSource;
    198                }
    199                catch (Exception e)
    200                {
    201                    log.warn("Failed to create InputSource for " + resolved + " ("
    202                                    + e.toString() + ")");
    203                    return null;
    204                }
    205            }
    206
    207            return null;
    208        }
    209
    210        /**
    211         * Returns the logger used by this configuration object.
    212         *
    213         * @return the logger
    214         */
    215        public Log getLogger()
    216        {
    217            return log;
    218        }
    219
    220        /**
    221         * Allows to set the logger to be used by this configuration object. This
    222         * method makes it possible for clients to exactly control logging behavior.
    223         * Per default a logger is set that will ignore all log messages. Derived
    224         * classes that want to enable logging should call this method during their
    225         * initialization with the logger to be used.
    226         *
    227         * @param log the new logger
    228         */
    229        public void setLogger(Log log)
    230        {
    231            this.log = (log != null) ? log : LogFactory.getLog(CatalogResolver.class);
    232        }
    233
    234        private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver()
    235        {
    236            if (resolver == null)
    237            {
    238                resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager);
    239            }
    240            return resolver;
    241        }
    242
    243        /**
    244         * Extend the CatalogManager to make the FileSystem and base directory accessible.
    245         */
    246        public static class CatalogManager extends org.apache.xml.resolver.CatalogManager
    247        {
    248            /** The static catalog used by this manager. */
    249            private static org.apache.xml.resolver.Catalog staticCatalog;
    250
    251            /** The FileSystem */
    252            private FileSystem fs;
    253
    254            /** The base directory */
    255            private String baseDir = System.getProperty("user.dir");
    256
    257            /** The String Substitutor */
    258            private StrSubstitutor substitutor;
    259
    260            /**
    261             * Set the FileSystem
    262             * @param fileSystem The FileSystem in use.
    263             */
    264            public void setFileSystem(FileSystem fileSystem)
    265            {
    266                this.fs = fileSystem;
    267            }
    268
    269            /**
    270             * Retrieve the FileSystem.
    271             * @return The FileSystem.
    272             */
    273            public FileSystem getFileSystem()
    274            {
    275                return this.fs;
    276            }
    277
    278            /**
    279             * Set the base directory.
    280             * @param baseDir The base directory.
    281             */
    282            public void setBaseDir(String baseDir)
    283            {
    284                if (baseDir != null)
    285                {
    286                    this.baseDir = baseDir;
    287                }
    288            }
    289
    290            /**
    291             * Return the base directory.
    292             * @return The base directory.
    293             */
    294            public String getBaseDir()
    295            {
    296                return this.baseDir;
    297            }
    298
    299            public void setSubstitutor(StrSubstitutor substitutor)
    300            {
    301                this.substitutor = substitutor;
    302            }
    303
    304            public StrSubstitutor getStrSubstitutor()
    305            {
    306                return this.substitutor;
    307            }
    308
    309
    310            /**
    311             * Get a new catalog instance. This method is only overridden because xml-resolver
    312             * might be in a parent ClassLoader and will be incapable of loading our Catalog
    313             * implementation.
    314             *
    315             * This method always returns a new instance of the underlying catalog class.
    316             * @return the Catalog.
    317             */
    318            @Override
    319            public org.apache.xml.resolver.Catalog getPrivateCatalog()
    320            {
    321                org.apache.xml.resolver.Catalog catalog = staticCatalog;
    322
    323                if (catalog == null || !getUseStaticCatalog())
    324                {
    325                    try
    326                    {
    327                        catalog = new Catalog();
    328                        catalog.setCatalogManager(this);
    329                        catalog.setupReaders();
    330                        catalog.loadSystemCatalogs();
    331                    }
    332                    catch (Exception ex)
    333                    {
    334                        ex.printStackTrace();
    335                    }
    336
    337                    if (getUseStaticCatalog())
    338                    {
    339                        staticCatalog = catalog;
    340                    }
    341                }
    342
    343                return catalog;
    344            }
    345
    346            /**
    347             * Get a catalog instance.
    348             *
    349             * If this manager uses static catalogs, the same static catalog will
    350             * always be returned. Otherwise a new catalog will be returned.
    351             * @return The Catalog.
    352             */
    353            @Override
    354            public org.apache.xml.resolver.Catalog getCatalog()
    355            {
    356                return getPrivateCatalog();
    357            }
    358        }
    359
    360        /**
    361         * Overrides the Catalog implementation to use the underlying FileSystem.
    362         */
    363        public static class Catalog extends org.apache.xml.resolver.Catalog
    364        {
    365            /** The FileSystem */
    366            private FileSystem fs;
    367
    368            /** FileNameMap to determine the mime type */
    369            private FileNameMap fileNameMap = URLConnection.getFileNameMap();
    370
    371            /**
    372             * Load the catalogs.
    373             * @throws IOException if an error occurs.
    374             */
    375            @Override
    376            public void loadSystemCatalogs() throws IOException
    377            {
    378                fs = ((CatalogManager) catalogManager).getFileSystem();
    379                String base = ((CatalogManager) catalogManager).getBaseDir();
    380
    381                // This is safe because the catalog manager returns a vector of strings.
    382                @SuppressWarnings("unchecked")
    383                Vector<String> catalogs = catalogManager.getCatalogFiles();
    384                if (catalogs != null)
    385                {
    386                    for (int count = 0; count < catalogs.size(); count++)
    387                    {
    388                        String fileName = (String) catalogs.elementAt(count);
    389
    390                        URL url = null;
    391                        InputStream is = null;
    392
    393                        try
    394                        {
    395                            url = ConfigurationUtils.locate(fs, base, fileName);
    396                            if (url != null)
    397                            {
    398                                is = fs.getInputStream(url);
    399                            }
    400                        }
    401                        catch (ConfigurationException ce)
    402                        {
    403                            String name = (url == null) ? fileName : url.toString();
    404                            // Ignore the exception.
    405                            catalogManager.debug.message(DEBUG_ALL,
    406                                "Unable to get input stream for " + name + ". " + ce.getMessage());
    407                        }
    408                        if (is != null)
    409                        {
    410                            String mimeType = fileNameMap.getContentTypeFor(fileName);
    411                            try
    412                            {
    413                                if (mimeType != null)
    414                                {
    415                                    parseCatalog(mimeType, is);
    416                                    continue;
    417                                }
    418                            }
    419                            catch (Exception ex)
    420                            {
    421                                // Ignore the exception.
    422                                catalogManager.debug.message(DEBUG_ALL,
    423                                    "Exception caught parsing input stream for " + fileName + ". "
    424                                    + ex.getMessage());
    425                            }
    426                            finally
    427                            {
    428                                is.close();
    429                            }
    430                        }
    431                        parseCatalog(base, fileName);
    432                    }
    433                }
    434
    435            }
    436
    437            /**
    438             * Parse the specified catalog file.
    439             * @param baseDir The base directory, if not included in the file name.
    440             * @param fileName The catalog file. May be a full URI String.
    441             * @throws IOException If an error occurs.
    442             */
    443            public void parseCatalog(String baseDir, String fileName) throws IOException
    444            {
    445                base = ConfigurationUtils.locate(fs, baseDir, fileName);
    446                catalogCwd = base;
    447                default_override = catalogManager.getPreferPublic();
    448                catalogManager.debug.message(DEBUG_NORMAL, "Parse catalog: " + fileName);
    449
    450                boolean parsed = false;
    451
    452                for (int count = 0; !parsed && count < readerArr.size(); count++)
    453                {
    454                    CatalogReader reader = (CatalogReader) readerArr.get(count);
    455                    InputStream inStream;
    456
    457                    try
    458                    {
    459                        inStream = fs.getInputStream(base);
    460                    }
    461                    catch (Exception ex)
    462                    {
    463                        catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base
    464                            + ex.getMessage());
    465                        break;
    466                    }
    467
    468                    try
    469                    {
    470                        reader.readCatalog(this, inStream);
    471                        parsed = true;
    472                    }
    473                    catch (CatalogException ce)
    474                    {
    475                        catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName
    476                                + ce.getMessage());
    477                        if (ce.getExceptionType() == CatalogException.PARSE_FAILED)
    478                        {
    479                            break;
    480                        }
    481                        else
    482                        {
    483                            // try again!
    484                            continue;
    485                        }
    486                    }
    487                    finally
    488                    {
    489                        try
    490                        {
    491                            inStream.close();
    492                        }
    493                        catch (IOException ioe)
    494                        {
    495                            // Ignore the exception.
    496                            inStream = null;
    497                        }
    498                    }
    499                }
    500
    501                if (parsed)
    502                {
    503                    parsePendingCatalogs();
    504                }
    505            }
    506
    507            /**
    508             * Perform character normalization on a URI reference.
    509             *
    510             * @param uriref The URI reference
    511             * @return The normalized URI reference.
    512             */
    513            @Override
    514            protected String normalizeURI(String uriref)
    515            {
    516                StrSubstitutor substitutor = ((CatalogManager) catalogManager).getStrSubstitutor();
    517                String resolved = substitutor != null ? substitutor.replace(uriref) : uriref;
    518                return super.normalizeURI(resolved);
    519            }
    520        }
    521    }