| /* |
| * 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.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 = System.getProperty("user.dir"); |
| |
| /** 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 <b>null</b> as |
| * argument disables logging. |
| * |
| * @param log the new logger |
| */ |
| public void setLogger(final ConfigurationLogger log) { |
| initLogger(log); |
| } |
| } |