/*
 * 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.configuration2.resolver;

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

import org.apache.commons.configuration2.io.ConfigurationLogger;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration2.io.FileLocator;
import org.apache.commons.configuration2.io.FileLocatorUtils;
import org.apache.commons.configuration2.io.FileSystem;
import org.apache.xml.resolver.CatalogException;
import org.apache.xml.resolver.readers.CatalogReader;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

Thin wrapper around xml commons CatalogResolver to allow list of catalogs to be provided.
Since:1.7
/** * Thin wrapper around xml commons CatalogResolver to allow list of catalogs * to be provided. * @since 1.7 */
public class CatalogResolver implements EntityResolver {
Debug everything.
/** * Debug everything. */
private static final int DEBUG_ALL = 9;
Normal debug setting.
/** * Normal debug setting. */
private static final int DEBUG_NORMAL = 4;
Debug nothing.
/** * Debug nothing. */
private static final int DEBUG_NONE = 0;
The CatalogManager
/** * The CatalogManager */
private final CatalogManager manager = new CatalogManager();
The FileSystem in use.
/** * The FileSystem in use. */
private FileSystem fs = FileLocatorUtils.DEFAULT_FILE_SYSTEM;
The CatalogResolver
/** * The CatalogResolver */
private org.apache.xml.resolver.tools.CatalogResolver resolver;
Stores the logger.
/** * Stores the logger. */
private ConfigurationLogger log;
Constructs the CatalogResolver
/** * Constructs the CatalogResolver */
public CatalogResolver() { manager.setIgnoreMissingProperties(true); manager.setUseStaticCatalog(false); manager.setFileSystem(fs); initLogger(null); }
Sets the list of catalog file names
Params:
  • catalogs – The delimited list of catalog files.
/** * Sets the list of catalog file names * * @param catalogs The delimited list of catalog files. */
public void setCatalogFiles(final String catalogs) { manager.setCatalogFiles(catalogs); }
Sets the FileSystem.
Params:
  • fileSystem – The FileSystem.
/** * Sets the FileSystem. * @param fileSystem The FileSystem. */
public void setFileSystem(final FileSystem fileSystem) { this.fs = fileSystem; manager.setFileSystem(fileSystem); }
Sets the base path.
Params:
  • baseDir – The base path String.
/** * Sets the base path. * @param baseDir The base path String. */
public void setBaseDir(final String baseDir) { manager.setBaseDir(baseDir); }
Sets the ConfigurationInterpolator.
Params:
  • ci – the ConfigurationInterpolator
/** * Sets the {@code ConfigurationInterpolator}. * @param ci the {@code ConfigurationInterpolator} */
public void setInterpolator(final ConfigurationInterpolator ci) { manager.setInterpolator(ci); }
Enables debug logging of xml-commons Catalog processing.
Params:
  • debug – True if debugging should be enabled, false otherwise.
/** * Enables debug logging of xml-commons Catalog processing. * @param debug True if debugging should be enabled, false otherwise. */
public void setDebug(final boolean debug) { if (debug) { manager.setVerbosity(DEBUG_ALL); } else { manager.setVerbosity(DEBUG_NONE); } }

Implements the resolveEntity method for the SAX interface.

Presented with an optional public identifier and a system identifier, this function attempts to locate a mapping in the catalogs.

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.

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.

Params:
  • publicId – The public identifier for the entity in question. This may be null.
  • systemId – The system identifier for the entity in question. XML requires a system identifier on all external entities, so this value is always specified.
Throws:
Returns:An InputSource for the mapped identifier, or null.
/** * <p> * Implements the {@code resolveEntity} 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>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>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. */
@SuppressWarnings("resource") // InputSource wraps an InputStream. @Override public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException { String resolved = getResolver().getResolvedEntity(publicId, systemId); if (resolved != null) { final String badFilePrefix = "file://"; final 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 { final URL url = locate(fs, null, resolved); if (url == null) { throw new ConfigurationException("Could not locate " + resolved); } final InputStream inputStream = fs.getInputStream(url); final InputSource inputSource = new InputSource(resolved); inputSource.setPublicId(publicId); inputSource.setByteStream(inputStream); return inputSource; } catch (final Exception e) { log.warn("Failed to create InputSource for " + resolved, e); return null; } } return null; }
Gets the logger used by this configuration object.
Returns:the logger
/** * Gets the logger used by this configuration object. * * @return the logger */
public ConfigurationLogger getLogger() { return log; }
Allows setting the logger to be used by this 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. Passing in null as argument disables logging.
Params:
  • log – the new logger
/** * Allows setting the logger to be used by this 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. Passing in <b>null</b> as * argument disables logging. * * @param log the new logger */
public void setLogger(final ConfigurationLogger log) { initLogger(log); }
Initializes the logger. Checks for null parameters.
Params:
  • log – the new logger
/** * Initializes the logger. Checks for null parameters. * * @param log the new logger */
private void initLogger(final ConfigurationLogger log) { this.log = log != null ? log : ConfigurationLogger.newDummyLogger(); } private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver() { if (resolver == null) { resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager); } return resolver; }
Locates a given file. This implementation delegates to the corresponding method in FileLocatorUtils.
Params:
  • fs – the FileSystem
  • basePath – the base path
  • name – the file name
Returns:the URL pointing to the file
/** * Locates a given file. This implementation delegates to * the corresponding method in {@link FileLocatorUtils}. * * @param fs the {@code FileSystem} * @param basePath the base path * @param name the file name * @return the URL pointing to the file */
private static URL locate(final FileSystem fs, final String basePath, final String name) { final FileLocator locator = FileLocatorUtils.fileLocator().fileSystem(fs) .basePath(basePath).fileName(name).create(); return FileLocatorUtils.locate(locator); }
Extends the CatalogManager to make the FileSystem and base directory accessible.
/** * Extends 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.
/** The static catalog used by this manager. */
private static org.apache.xml.resolver.Catalog staticCatalog;
The FileSystem
/** The FileSystem */
private FileSystem fs;
The base directory
/** The base directory */
private String baseDir = System.getProperty("user.dir");
The object for handling interpolation.
/** The object for handling interpolation. */
private ConfigurationInterpolator interpolator;
Sets the FileSystem
Params:
  • fileSystem – The FileSystem in use.
/** * Sets the FileSystem * @param fileSystem The FileSystem in use. */
public void setFileSystem(final FileSystem fileSystem) { this.fs = fileSystem; }
Gets the FileSystem.
Returns:The FileSystem.
/** * Gets the FileSystem. * @return The FileSystem. */
public FileSystem getFileSystem() { return this.fs; }
Sets the base directory.
Params:
  • baseDir – The base directory.
/** * Sets the base directory. * @param baseDir The base directory. */
public void setBaseDir(final String baseDir) { if (baseDir != null) { this.baseDir = baseDir; } }
Gets the base directory.
Returns:The base directory.
/** * Gets the base directory. * @return The base directory. */
public String getBaseDir() { return this.baseDir; }
Sets the ConfigurationInterpolator.
Params:
  • configurationInterpolator – the ConfigurationInterpolator.
/** * Sets the ConfigurationInterpolator. * * @param configurationInterpolator the ConfigurationInterpolator. */
public void setInterpolator(final ConfigurationInterpolator configurationInterpolator) { interpolator = configurationInterpolator; }
Gets the ConfigurationInterpolator.
Returns:the ConfigurationInterpolator.
/** * Gets the ConfigurationInterpolator. * * @return the ConfigurationInterpolator. */
public ConfigurationInterpolator getInterpolator() { return interpolator; }
Gets 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.
Returns:the Catalog.
/** * Gets 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. */
@Override 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 (final Exception ex) { ex.printStackTrace(); } if (getUseStaticCatalog()) { staticCatalog = catalog; } } return catalog; }
Gets a catalog instance. If this manager uses static catalogs, the same static catalog will always be returned. Otherwise a new catalog will be returned.
Returns:The Catalog.
/** * Gets 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. */
@Override public org.apache.xml.resolver.Catalog getCatalog() { return getPrivateCatalog(); } }
Overrides the Catalog implementation to use the underlying FileSystem.
/** * Overrides the Catalog implementation to use the underlying FileSystem. */
public static class Catalog extends org.apache.xml.resolver.Catalog {
The FileSystem
/** The FileSystem */
private FileSystem fs;
FileNameMap to determine the mime type
/** FileNameMap to determine the mime type */
private final FileNameMap fileNameMap = URLConnection.getFileNameMap();
Load the catalogs.
Throws:
  • IOException – if an error occurs.
/** * Load the catalogs. * @throws IOException if an error occurs. */
@Override public void loadSystemCatalogs() throws IOException { fs = ((CatalogManager) catalogManager).getFileSystem(); final String base = ((CatalogManager) catalogManager).getBaseDir(); // This is safe because the catalog manager returns a vector of strings. final Vector<String> catalogs = catalogManager.getCatalogFiles(); if (catalogs != null) { for (int count = 0; count < catalogs.size(); count++) { final String fileName = catalogs.elementAt(count); URL url = null; InputStream inputStream = null; try { url = locate(fs, base, fileName); if (url != null) { inputStream = fs.getInputStream(url); } } catch (final ConfigurationException ce) { final String name = url.toString(); // Ignore the exception. catalogManager.debug.message(DEBUG_ALL, "Unable to get input stream for " + name + ". " + ce.getMessage()); } if (inputStream != null) { final String mimeType = fileNameMap.getContentTypeFor(fileName); try { if (mimeType != null) { parseCatalog(mimeType, inputStream); continue; } } catch (final Exception ex) { // Ignore the exception. catalogManager.debug.message(DEBUG_ALL, "Exception caught parsing input stream for " + fileName + ". " + ex.getMessage()); } finally { inputStream.close(); } } parseCatalog(base, fileName); } } }
Parses the specified catalog file.
Params:
  • baseDir – The base directory, if not included in the file name.
  • fileName – The catalog file. May be a full URI String.
Throws:
/** * Parses 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(final String baseDir, final String fileName) throws IOException { base = 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++) { final CatalogReader reader = (CatalogReader) readerArr.get(count); InputStream inputStream; try { inputStream = fs.getInputStream(base); } catch (final Exception ex) { catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base + ex.getMessage()); break; } try { reader.readCatalog(this, inputStream); parsed = true; } catch (final CatalogException ce) { catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName + ce.getMessage()); if (ce.getExceptionType() == CatalogException.PARSE_FAILED) { break; } // try again! continue; } finally { try { inputStream.close(); } catch (final IOException ioe) { // Ignore the exception. inputStream = null; } } } if (parsed) { parsePendingCatalogs(); } }
Performs character normalization on a URI reference.
Params:
  • uriref – The URI reference
Returns:The normalized URI reference.
/** * Performs character normalization on a URI reference. * * @param uriref The URI reference * @return The normalized URI reference. */
@Override protected String normalizeURI(final String uriref) { final ConfigurationInterpolator ci = ((CatalogManager) catalogManager).getInterpolator(); final String resolved = ci != null ? String.valueOf(ci.interpolate(uriref)) : uriref; return super.normalizeURI(resolved); } } }