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.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.ex.ConfigurationException;
- import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
- import org.apache.commons.configuration2.io.ConfigurationLogger;
- import org.apache.commons.configuration2.io.FileLocatorUtils;
- import org.apache.commons.configuration2.io.FileSystem;
- import org.apache.commons.lang3.SystemProperties;
- 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
- */
- public class CatalogResolver implements EntityResolver {
- /**
- * 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 final FileNameMap fileNameMap = URLConnection.getFileNameMap();
- /**
- * 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);
- }
- }
- }
- /**
- * 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);
- }
- /**
- * 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();
- }
- }
- }
- /**
- * 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. */
- private static org.apache.xml.resolver.Catalog staticCatalog;
- /** The FileSystem */
- private FileSystem fs;
- /** The base directory */
- private String baseDir = SystemProperties.getUserDir();
- /** The object for handling interpolation. */
- private ConfigurationInterpolator interpolator;
- /**
- * Gets the base directory.
- *
- * @return The base directory.
- */
- public String getBaseDir() {
- return this.baseDir;
- }
- /**
- * 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();
- }
- /**
- * Gets the FileSystem.
- *
- * @return The FileSystem.
- */
- public FileSystem getFileSystem() {
- return this.fs;
- }
- /**
- * 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.
- *
- * @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;
- }
- /**
- * Sets the base directory.
- *
- * @param baseDir The base directory.
- */
- public void setBaseDir(final String baseDir) {
- if (baseDir != null) {
- this.baseDir = baseDir;
- }
- }
- /**
- * Sets the FileSystem
- *
- * @param fileSystem The FileSystem in use.
- */
- public void setFileSystem(final FileSystem fileSystem) {
- this.fs = fileSystem;
- }
- /**
- * Sets the ConfigurationInterpolator.
- *
- * @param configurationInterpolator the ConfigurationInterpolator.
- */
- public void setInterpolator(final ConfigurationInterpolator configurationInterpolator) {
- interpolator = configurationInterpolator;
- }
- }
- /**
- * 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;
- /**
- * 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) {
- return FileLocatorUtils.locate(FileLocatorUtils.fileLocator().fileSystem(fs).basePath(basePath).fileName(name).create());
- }
- /**
- * The CatalogManager
- */
- private final CatalogManager manager = new CatalogManager();
- /**
- * The FileSystem in use.
- */
- private FileSystem fs = FileLocatorUtils.DEFAULT_FILE_SYSTEM;
- /**
- * The CatalogResolver
- */
- private org.apache.xml.resolver.tools.CatalogResolver resolver;
- /**
- * Stores the logger.
- */
- private ConfigurationLogger log;
- /**
- * Constructs the CatalogResolver
- */
- public CatalogResolver() {
- manager.setIgnoreMissingProperties(true);
- manager.setUseStaticCatalog(false);
- manager.setFileSystem(fs);
- initLogger(null);
- }
- /**
- * Gets the logger used by this configuration object.
- *
- * @return the logger
- */
- public ConfigurationLogger getLogger() {
- return log;
- }
- private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver() {
- if (resolver == null) {
- resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager);
- }
- return resolver;
- }
- /**
- * 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();
- }
- /**
- * <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;
- }
- /**
- * Sets the base path.
- *
- * @param baseDir The base path String.
- */
- public void setBaseDir(final String baseDir) {
- manager.setBaseDir(baseDir);
- }
- /**
- * Sets the list of catalog file names
- *
- * @param catalogs The delimited list of catalog files.
- */
- public void setCatalogFiles(final String catalogs) {
- manager.setCatalogFiles(catalogs);
- }
- /**
- * Enables debug logging of xml-commons Catalog processing.
- *
- * @param debug True if debugging should be enabled, false otherwise.
- */
- public void setDebug(final boolean debug) {
- manager.setVerbosity(debug ? DEBUG_ALL : DEBUG_NONE);
- }
- /**
- * Sets the FileSystem.
- *
- * @param fileSystem The FileSystem.
- */
- public void setFileSystem(final FileSystem fileSystem) {
- this.fs = fileSystem;
- manager.setFileSystem(fileSystem);
- }
- /**
- * Sets the {@code ConfigurationInterpolator}.
- *
- * @param ci the {@code ConfigurationInterpolator}
- */
- public void setInterpolator(final ConfigurationInterpolator ci) {
- manager.setInterpolator(ci);
- }
- /**
- * 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 <strong>null</strong> as
- * argument disables logging.
- *
- * @param log the new logger
- */
- public void setLogger(final ConfigurationLogger log) {
- initLogger(log);
- }
- }