CatalogResolver.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.resolver;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.net.FileNameMap;
  21. import java.net.URL;
  22. import java.net.URLConnection;
  23. import java.util.Vector;

  24. import org.apache.commons.configuration2.ex.ConfigurationException;
  25. import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
  26. import org.apache.commons.configuration2.io.ConfigurationLogger;
  27. import org.apache.commons.configuration2.io.FileLocatorUtils;
  28. import org.apache.commons.configuration2.io.FileSystem;
  29. import org.apache.commons.lang3.SystemProperties;
  30. import org.apache.xml.resolver.CatalogException;
  31. import org.apache.xml.resolver.readers.CatalogReader;
  32. import org.xml.sax.EntityResolver;
  33. import org.xml.sax.InputSource;
  34. import org.xml.sax.SAXException;

  35. /**
  36.  * Thin wrapper around xml commons CatalogResolver to allow list of catalogs to be provided.
  37.  *
  38.  * @since 1.7
  39.  */
  40. public class CatalogResolver implements EntityResolver {
  41.     /**
  42.      * Overrides the Catalog implementation to use the underlying FileSystem.
  43.      */
  44.     public static class Catalog extends org.apache.xml.resolver.Catalog {
  45.         /** The FileSystem */
  46.         private FileSystem fs;

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

  49.         /**
  50.          * Load the catalogs.
  51.          *
  52.          * @throws IOException if an error occurs.
  53.          */
  54.         @Override
  55.         public void loadSystemCatalogs() throws IOException {
  56.             fs = ((CatalogManager) catalogManager).getFileSystem();
  57.             final String base = ((CatalogManager) catalogManager).getBaseDir();

  58.             // This is safe because the catalog manager returns a vector of strings.
  59.             final Vector<String> catalogs = catalogManager.getCatalogFiles();
  60.             if (catalogs != null) {
  61.                 for (int count = 0; count < catalogs.size(); count++) {
  62.                     final String fileName = catalogs.elementAt(count);

  63.                     URL url = null;
  64.                     InputStream inputStream = null;

  65.                     try {
  66.                         url = locate(fs, base, fileName);
  67.                         if (url != null) {
  68.                             inputStream = fs.getInputStream(url);
  69.                         }
  70.                     } catch (final ConfigurationException ce) {
  71.                         final String name = url.toString();
  72.                         // Ignore the exception.
  73.                         catalogManager.debug.message(DEBUG_ALL, "Unable to get input stream for " + name + ". " + ce.getMessage());
  74.                     }
  75.                     if (inputStream != null) {
  76.                         final String mimeType = fileNameMap.getContentTypeFor(fileName);
  77.                         try {
  78.                             if (mimeType != null) {
  79.                                 parseCatalog(mimeType, inputStream);
  80.                                 continue;
  81.                             }
  82.                         } catch (final Exception ex) {
  83.                             // Ignore the exception.
  84.                             catalogManager.debug.message(DEBUG_ALL, "Exception caught parsing input stream for " + fileName + ". " + ex.getMessage());
  85.                         } finally {
  86.                             inputStream.close();
  87.                         }
  88.                     }
  89.                     parseCatalog(base, fileName);
  90.                 }
  91.             }

  92.         }

  93.         /**
  94.          * Performs character normalization on a URI reference.
  95.          *
  96.          * @param uriref The URI reference
  97.          * @return The normalized URI reference.
  98.          */
  99.         @Override
  100.         protected String normalizeURI(final String uriref) {
  101.             final ConfigurationInterpolator ci = ((CatalogManager) catalogManager).getInterpolator();
  102.             final String resolved = ci != null ? String.valueOf(ci.interpolate(uriref)) : uriref;
  103.             return super.normalizeURI(resolved);
  104.         }

  105.         /**
  106.          * Parses the specified catalog file.
  107.          *
  108.          * @param baseDir The base directory, if not included in the file name.
  109.          * @param fileName The catalog file. May be a full URI String.
  110.          * @throws IOException If an error occurs.
  111.          */
  112.         public void parseCatalog(final String baseDir, final String fileName) throws IOException {
  113.             base = locate(fs, baseDir, fileName);
  114.             catalogCwd = base;
  115.             default_override = catalogManager.getPreferPublic();
  116.             catalogManager.debug.message(DEBUG_NORMAL, "Parse catalog: " + fileName);

  117.             boolean parsed = false;

  118.             for (int count = 0; !parsed && count < readerArr.size(); count++) {
  119.                 final CatalogReader reader = (CatalogReader) readerArr.get(count);
  120.                 InputStream inputStream;

  121.                 try {
  122.                     inputStream = fs.getInputStream(base);
  123.                 } catch (final Exception ex) {
  124.                     catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base + ex.getMessage());
  125.                     break;
  126.                 }

  127.                 try {
  128.                     reader.readCatalog(this, inputStream);
  129.                     parsed = true;
  130.                 } catch (final CatalogException ce) {
  131.                     catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName + ce.getMessage());
  132.                     if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
  133.                         break;
  134.                     }
  135.                     // try again!
  136.                     continue;
  137.                 } finally {
  138.                     try {
  139.                         inputStream.close();
  140.                     } catch (final IOException ioe) {
  141.                         // Ignore the exception.
  142.                         inputStream = null;
  143.                     }
  144.                 }
  145.             }

  146.             if (parsed) {
  147.                 parsePendingCatalogs();
  148.             }
  149.         }
  150.     }

  151.     /**
  152.      * Extends the CatalogManager to make the FileSystem and base directory accessible.
  153.      */
  154.     public static class CatalogManager extends org.apache.xml.resolver.CatalogManager {
  155.         /** The static catalog used by this manager. */
  156.         private static org.apache.xml.resolver.Catalog staticCatalog;

  157.         /** The FileSystem */
  158.         private FileSystem fs;

  159.         /** The base directory */
  160.         private String baseDir = SystemProperties.getUserDir();

  161.         /** The object for handling interpolation. */
  162.         private ConfigurationInterpolator interpolator;

  163.         /**
  164.          * Gets the base directory.
  165.          *
  166.          * @return The base directory.
  167.          */
  168.         public String getBaseDir() {
  169.             return this.baseDir;
  170.         }

  171.         /**
  172.          * Gets a catalog instance.
  173.          *
  174.          * If this manager uses static catalogs, the same static catalog will always be returned. Otherwise a new catalog will
  175.          * be returned.
  176.          *
  177.          * @return The Catalog.
  178.          */
  179.         @Override
  180.         public org.apache.xml.resolver.Catalog getCatalog() {
  181.             return getPrivateCatalog();
  182.         }

  183.         /**
  184.          * Gets the FileSystem.
  185.          *
  186.          * @return The FileSystem.
  187.          */
  188.         public FileSystem getFileSystem() {
  189.             return this.fs;
  190.         }

  191.         /**
  192.          * Gets the ConfigurationInterpolator.
  193.          *
  194.          * @return the ConfigurationInterpolator.
  195.          */
  196.         public ConfigurationInterpolator getInterpolator() {
  197.             return interpolator;
  198.         }

  199.         /**
  200.          * Gets a new catalog instance. This method is only overridden because xml-resolver might be in a parent ClassLoader and
  201.          * will be incapable of loading our Catalog implementation.
  202.          *
  203.          * This method always returns a new instance of the underlying catalog class.
  204.          *
  205.          * @return the Catalog.
  206.          */
  207.         @Override
  208.         public org.apache.xml.resolver.Catalog getPrivateCatalog() {
  209.             org.apache.xml.resolver.Catalog catalog = staticCatalog;

  210.             if (catalog == null || !getUseStaticCatalog()) {
  211.                 try {
  212.                     catalog = new Catalog();
  213.                     catalog.setCatalogManager(this);
  214.                     catalog.setupReaders();
  215.                     catalog.loadSystemCatalogs();
  216.                 } catch (final Exception ex) {
  217.                     ex.printStackTrace();
  218.                 }

  219.                 if (getUseStaticCatalog()) {
  220.                     staticCatalog = catalog;
  221.                 }
  222.             }

  223.             return catalog;
  224.         }

  225.         /**
  226.          * Sets the base directory.
  227.          *
  228.          * @param baseDir The base directory.
  229.          */
  230.         public void setBaseDir(final String baseDir) {
  231.             if (baseDir != null) {
  232.                 this.baseDir = baseDir;
  233.             }
  234.         }

  235.         /**
  236.          * Sets the FileSystem
  237.          *
  238.          * @param fileSystem The FileSystem in use.
  239.          */
  240.         public void setFileSystem(final FileSystem fileSystem) {
  241.             this.fs = fileSystem;
  242.         }

  243.         /**
  244.          * Sets the ConfigurationInterpolator.
  245.          *
  246.          * @param configurationInterpolator the ConfigurationInterpolator.
  247.          */
  248.         public void setInterpolator(final ConfigurationInterpolator configurationInterpolator) {
  249.             interpolator = configurationInterpolator;
  250.         }
  251.     }

  252.     /**
  253.      * Debug everything.
  254.      */
  255.     private static final int DEBUG_ALL = 9;

  256.     /**
  257.      * Normal debug setting.
  258.      */
  259.     private static final int DEBUG_NORMAL = 4;

  260.     /**
  261.      * Debug nothing.
  262.      */
  263.     private static final int DEBUG_NONE = 0;

  264.     /**
  265.      * Locates a given file. This implementation delegates to the corresponding method in {@link FileLocatorUtils}.
  266.      *
  267.      * @param fs the {@code FileSystem}
  268.      * @param basePath the base path
  269.      * @param name the file name
  270.      * @return the URL pointing to the file
  271.      */
  272.     private static URL locate(final FileSystem fs, final String basePath, final String name) {
  273.         return FileLocatorUtils.locate(FileLocatorUtils.fileLocator().fileSystem(fs).basePath(basePath).fileName(name).create());
  274.     }

  275.     /**
  276.      * The CatalogManager
  277.      */
  278.     private final CatalogManager manager = new CatalogManager();

  279.     /**
  280.      * The FileSystem in use.
  281.      */
  282.     private FileSystem fs = FileLocatorUtils.DEFAULT_FILE_SYSTEM;

  283.     /**
  284.      * The CatalogResolver
  285.      */
  286.     private org.apache.xml.resolver.tools.CatalogResolver resolver;

  287.     /**
  288.      * Stores the logger.
  289.      */
  290.     private ConfigurationLogger log;

  291.     /**
  292.      * Constructs the CatalogResolver
  293.      */
  294.     public CatalogResolver() {
  295.         manager.setIgnoreMissingProperties(true);
  296.         manager.setUseStaticCatalog(false);
  297.         manager.setFileSystem(fs);
  298.         initLogger(null);
  299.     }

  300.     /**
  301.      * Gets the logger used by this configuration object.
  302.      *
  303.      * @return the logger
  304.      */
  305.     public ConfigurationLogger getLogger() {
  306.         return log;
  307.     }

  308.     private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver() {
  309.         if (resolver == null) {
  310.             resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager);
  311.         }
  312.         return resolver;
  313.     }

  314.     /**
  315.      * Initializes the logger. Checks for null parameters.
  316.      *
  317.      * @param log the new logger
  318.      */
  319.     private void initLogger(final ConfigurationLogger log) {
  320.         this.log = log != null ? log : ConfigurationLogger.newDummyLogger();
  321.     }

  322.     /**
  323.      * <p>
  324.      * Implements the {@code resolveEntity} method for the SAX interface.
  325.      * </p>
  326.      * <p>
  327.      * Presented with an optional public identifier and a system identifier, this function attempts to locate a mapping in
  328.      * the catalogs.
  329.      * </p>
  330.      * <p>
  331.      * If such a mapping is found, the resolver attempts to open the mapped value as an InputSource and return it.
  332.      * Exceptions are ignored and null is returned if the mapped value cannot be opened as an input source.
  333.      * </p>
  334.      * <p>
  335.      * If no mapping is found (or an error occurs attempting to open the mapped value as an input source), null is returned
  336.      * and the system will use the specified system identifier as if no entityResolver was specified.
  337.      * </p>
  338.      *
  339.      * @param publicId The public identifier for the entity in question. This may be null.
  340.      * @param systemId The system identifier for the entity in question. XML requires a system identifier on all external
  341.      *        entities, so this value is always specified.
  342.      * @return An InputSource for the mapped identifier, or null.
  343.      * @throws SAXException if an error occurs.
  344.      */
  345.     @SuppressWarnings("resource") // InputSource wraps an InputStream.
  346.     @Override
  347.     public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException {
  348.         String resolved = getResolver().getResolvedEntity(publicId, systemId);

  349.         if (resolved != null) {
  350.             final String badFilePrefix = "file://";
  351.             final String correctFilePrefix = "file:///";

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

  356.             try {
  357.                 final URL url = locate(fs, null, resolved);
  358.                 if (url == null) {
  359.                     throw new ConfigurationException("Could not locate " + resolved);
  360.                 }
  361.                 final InputStream inputStream = fs.getInputStream(url);
  362.                 final InputSource inputSource = new InputSource(resolved);
  363.                 inputSource.setPublicId(publicId);
  364.                 inputSource.setByteStream(inputStream);
  365.                 return inputSource;
  366.             } catch (final Exception e) {
  367.                 log.warn("Failed to create InputSource for " + resolved, e);
  368.             }
  369.         }

  370.         return null;
  371.     }

  372.     /**
  373.      * Sets the base path.
  374.      *
  375.      * @param baseDir The base path String.
  376.      */
  377.     public void setBaseDir(final String baseDir) {
  378.         manager.setBaseDir(baseDir);
  379.     }

  380.     /**
  381.      * Sets the list of catalog file names
  382.      *
  383.      * @param catalogs The delimited list of catalog files.
  384.      */
  385.     public void setCatalogFiles(final String catalogs) {
  386.         manager.setCatalogFiles(catalogs);
  387.     }

  388.     /**
  389.      * Enables debug logging of xml-commons Catalog processing.
  390.      *
  391.      * @param debug True if debugging should be enabled, false otherwise.
  392.      */
  393.     public void setDebug(final boolean debug) {
  394.         manager.setVerbosity(debug ? DEBUG_ALL : DEBUG_NONE);
  395.     }

  396.     /**
  397.      * Sets the FileSystem.
  398.      *
  399.      * @param fileSystem The FileSystem.
  400.      */
  401.     public void setFileSystem(final FileSystem fileSystem) {
  402.         this.fs = fileSystem;
  403.         manager.setFileSystem(fileSystem);
  404.     }

  405.     /**
  406.      * Sets the {@code ConfigurationInterpolator}.
  407.      *
  408.      * @param ci the {@code ConfigurationInterpolator}
  409.      */
  410.     public void setInterpolator(final ConfigurationInterpolator ci) {
  411.         manager.setInterpolator(ci);
  412.     }

  413.     /**
  414.      * Allows setting the logger to be used by this object. This method makes it possible for clients to exactly control
  415.      * logging behavior. Per default a logger is set that will ignore all log messages. Derived classes that want to enable
  416.      * logging should call this method during their initialization with the logger to be used. Passing in <strong>null</strong> as
  417.      * argument disables logging.
  418.      *
  419.      * @param log the new logger
  420.      */
  421.     public void setLogger(final ConfigurationLogger log) {
  422.         initLogger(log);
  423.     }
  424. }