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 */ 017package org.apache.commons.configuration2.resolver; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.FileNameMap; 022import java.net.URL; 023import java.net.URLConnection; 024import java.util.Vector; 025 026import org.apache.commons.configuration2.io.ConfigurationLogger; 027import org.apache.commons.configuration2.ex.ConfigurationException; 028import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; 029import org.apache.commons.configuration2.io.FileLocator; 030import org.apache.commons.configuration2.io.FileLocatorUtils; 031import org.apache.commons.configuration2.io.FileSystem; 032import org.apache.xml.resolver.CatalogException; 033import org.apache.xml.resolver.readers.CatalogReader; 034import org.xml.sax.EntityResolver; 035import org.xml.sax.InputSource; 036import 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 1842194 2018-09-27 22:24:23Z ggregory $ 046 */ 047public 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 private final CatalogManager manager = new CatalogManager(); 068 069 /** 070 * The FileSystem in use. 071 */ 072 private FileSystem fs = FileLocatorUtils.DEFAULT_FILE_SYSTEM; 073 074 /** 075 * The CatalogResolver 076 */ 077 private org.apache.xml.resolver.tools.CatalogResolver resolver; 078 079 /** 080 * Stores the logger. 081 */ 082 private ConfigurationLogger 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 initLogger(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(final String catalogs) 101 { 102 manager.setCatalogFiles(catalogs); 103 } 104 105 /** 106 * Set the FileSystem. 107 * @param fileSystem The FileSystem. 108 */ 109 public void setFileSystem(final 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(final String baseDir) 120 { 121 manager.setBaseDir(baseDir); 122 } 123 124 /** 125 * Set the {@code ConfigurationInterpolator}. 126 * @param ci the {@code ConfigurationInterpolator} 127 */ 128 public void setInterpolator(final ConfigurationInterpolator ci) 129 { 130 manager.setInterpolator(ci); 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(final 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 * <p> 151 * Implements the {@code resolveEntity} method 152 * for the SAX interface. 153 * </p> 154 * <p>Presented with an optional public identifier and a system 155 * identifier, this function attempts to locate a mapping in the 156 * catalogs.</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>If no mapping is found (or an error occurs attempting to open 162 * the mapped value as an input source), null is returned and the system 163 * will use the specified system identifier as if no entityResolver 164 * was specified.</p> 165 * 166 * @param publicId The public identifier for the entity in question. 167 * This may be null. 168 * @param systemId The system identifier for the entity in question. 169 * XML requires a system identifier on all external entities, so this 170 * value is always specified. 171 * @return An InputSource for the mapped identifier, or null. 172 * @throws SAXException if an error occurs. 173 */ 174 @Override 175 public InputSource resolveEntity(final String publicId, final String systemId) 176 throws SAXException 177 { 178 String resolved = getResolver().getResolvedEntity(publicId, systemId); 179 180 if (resolved != null) 181 { 182 final String badFilePrefix = "file://"; 183 final 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 final URL url = locate(fs, null, resolved); 194 if (url == null) 195 { 196 throw new ConfigurationException("Could not locate " 197 + resolved); 198 } 199 final InputStream is = fs.getInputStream(url); 200 final InputSource iSource = new InputSource(resolved); 201 iSource.setPublicId(publicId); 202 iSource.setByteStream(is); 203 return iSource; 204 } 205 catch (final Exception e) 206 { 207 log.warn("Failed to create InputSource for " + resolved, e); 208 return null; 209 } 210 } 211 212 return null; 213 } 214 215 /** 216 * Returns the logger used by this configuration object. 217 * 218 * @return the logger 219 */ 220 public ConfigurationLogger getLogger() 221 { 222 return log; 223 } 224 225 /** 226 * Allows setting the logger to be used by this object. This 227 * method makes it possible for clients to exactly control logging behavior. 228 * Per default a logger is set that will ignore all log messages. Derived 229 * classes that want to enable logging should call this method during their 230 * initialization with the logger to be used. Passing in <b>null</b> as 231 * argument disables logging. 232 * 233 * @param log the new logger 234 */ 235 public void setLogger(final ConfigurationLogger log) 236 { 237 initLogger(log); 238 } 239 240 /** 241 * Initializes the logger. Checks for null parameters. 242 * 243 * @param log the new logger 244 */ 245 private void initLogger(final ConfigurationLogger log) 246 { 247 this.log = (log != null) ? log : ConfigurationLogger.newDummyLogger(); 248 } 249 250 private synchronized org.apache.xml.resolver.tools.CatalogResolver getResolver() 251 { 252 if (resolver == null) 253 { 254 resolver = new org.apache.xml.resolver.tools.CatalogResolver(manager); 255 } 256 return resolver; 257 } 258 259 /** 260 * Helper method for locating a given file. This implementation delegates to 261 * the corresponding method in {@link FileLocatorUtils}. 262 * 263 * @param fs the {@code FileSystem} 264 * @param basePath the base path 265 * @param name the file name 266 * @return the URL pointing to the file 267 */ 268 private static URL locate(final FileSystem fs, final String basePath, final String name) 269 { 270 final FileLocator locator = 271 FileLocatorUtils.fileLocator().fileSystem(fs) 272 .basePath(basePath).fileName(name).create(); 273 return FileLocatorUtils.locate(locator); 274 } 275 276 /** 277 * Extend the CatalogManager to make the FileSystem and base directory accessible. 278 */ 279 public static class CatalogManager extends org.apache.xml.resolver.CatalogManager 280 { 281 /** The static catalog used by this manager. */ 282 private static org.apache.xml.resolver.Catalog staticCatalog; 283 284 /** The FileSystem */ 285 private FileSystem fs; 286 287 /** The base directory */ 288 private String baseDir = System.getProperty("user.dir"); 289 290 /** The object for handling interpolation. */ 291 private ConfigurationInterpolator interpolator; 292 293 /** 294 * Set the FileSystem 295 * @param fileSystem The FileSystem in use. 296 */ 297 public void setFileSystem(final FileSystem fileSystem) 298 { 299 this.fs = fileSystem; 300 } 301 302 /** 303 * Retrieve the FileSystem. 304 * @return The FileSystem. 305 */ 306 public FileSystem getFileSystem() 307 { 308 return this.fs; 309 } 310 311 /** 312 * Set the base directory. 313 * @param baseDir The base directory. 314 */ 315 public void setBaseDir(final String baseDir) 316 { 317 if (baseDir != null) 318 { 319 this.baseDir = baseDir; 320 } 321 } 322 323 /** 324 * Return the base directory. 325 * @return The base directory. 326 */ 327 public String getBaseDir() 328 { 329 return this.baseDir; 330 } 331 332 public void setInterpolator(final ConfigurationInterpolator ci) 333 { 334 interpolator = ci; 335 } 336 337 public ConfigurationInterpolator getInterpolator() 338 { 339 return interpolator; 340 } 341 342 343 /** 344 * Get a new catalog instance. This method is only overridden because xml-resolver 345 * might be in a parent ClassLoader and will be incapable of loading our Catalog 346 * implementation. 347 * 348 * This method always returns a new instance of the underlying catalog class. 349 * @return the Catalog. 350 */ 351 @Override 352 public org.apache.xml.resolver.Catalog getPrivateCatalog() 353 { 354 org.apache.xml.resolver.Catalog catalog = staticCatalog; 355 356 if (catalog == null || !getUseStaticCatalog()) 357 { 358 try 359 { 360 catalog = new Catalog(); 361 catalog.setCatalogManager(this); 362 catalog.setupReaders(); 363 catalog.loadSystemCatalogs(); 364 } 365 catch (final Exception ex) 366 { 367 ex.printStackTrace(); 368 } 369 370 if (getUseStaticCatalog()) 371 { 372 staticCatalog = catalog; 373 } 374 } 375 376 return catalog; 377 } 378 379 /** 380 * Get a catalog instance. 381 * 382 * If this manager uses static catalogs, the same static catalog will 383 * always be returned. Otherwise a new catalog will be returned. 384 * @return The Catalog. 385 */ 386 @Override 387 public org.apache.xml.resolver.Catalog getCatalog() 388 { 389 return getPrivateCatalog(); 390 } 391 } 392 393 /** 394 * Overrides the Catalog implementation to use the underlying FileSystem. 395 */ 396 public static class Catalog extends org.apache.xml.resolver.Catalog 397 { 398 /** The FileSystem */ 399 private FileSystem fs; 400 401 /** FileNameMap to determine the mime type */ 402 private final FileNameMap fileNameMap = URLConnection.getFileNameMap(); 403 404 /** 405 * Load the catalogs. 406 * @throws IOException if an error occurs. 407 */ 408 @Override 409 public void loadSystemCatalogs() throws IOException 410 { 411 fs = ((CatalogManager) catalogManager).getFileSystem(); 412 final String base = ((CatalogManager) catalogManager).getBaseDir(); 413 414 // This is safe because the catalog manager returns a vector of strings. 415 @SuppressWarnings("unchecked") 416 final 417 Vector<String> catalogs = catalogManager.getCatalogFiles(); 418 if (catalogs != null) 419 { 420 for (int count = 0; count < catalogs.size(); count++) 421 { 422 final String fileName = catalogs.elementAt(count); 423 424 URL url = null; 425 InputStream is = null; 426 427 try 428 { 429 url = locate(fs, base, fileName); 430 if (url != null) 431 { 432 is = fs.getInputStream(url); 433 } 434 } 435 catch (final ConfigurationException ce) 436 { 437 final String name = url.toString(); 438 // Ignore the exception. 439 catalogManager.debug.message(DEBUG_ALL, 440 "Unable to get input stream for " + name + ". " + ce.getMessage()); 441 } 442 if (is != null) 443 { 444 final String mimeType = fileNameMap.getContentTypeFor(fileName); 445 try 446 { 447 if (mimeType != null) 448 { 449 parseCatalog(mimeType, is); 450 continue; 451 } 452 } 453 catch (final Exception ex) 454 { 455 // Ignore the exception. 456 catalogManager.debug.message(DEBUG_ALL, 457 "Exception caught parsing input stream for " + fileName + ". " 458 + ex.getMessage()); 459 } 460 finally 461 { 462 is.close(); 463 } 464 } 465 parseCatalog(base, fileName); 466 } 467 } 468 469 } 470 471 /** 472 * Parse the specified catalog file. 473 * @param baseDir The base directory, if not included in the file name. 474 * @param fileName The catalog file. May be a full URI String. 475 * @throws IOException If an error occurs. 476 */ 477 public void parseCatalog(final String baseDir, final String fileName) throws IOException 478 { 479 base = locate(fs, baseDir, fileName); 480 catalogCwd = base; 481 default_override = catalogManager.getPreferPublic(); 482 catalogManager.debug.message(DEBUG_NORMAL, "Parse catalog: " + fileName); 483 484 boolean parsed = false; 485 486 for (int count = 0; !parsed && count < readerArr.size(); count++) 487 { 488 final CatalogReader reader = (CatalogReader) readerArr.get(count); 489 InputStream inStream; 490 491 try 492 { 493 inStream = fs.getInputStream(base); 494 } 495 catch (final Exception ex) 496 { 497 catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base 498 + ex.getMessage()); 499 break; 500 } 501 502 try 503 { 504 reader.readCatalog(this, inStream); 505 parsed = true; 506 } 507 catch (final CatalogException ce) 508 { 509 catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName 510 + ce.getMessage()); 511 if (ce.getExceptionType() == CatalogException.PARSE_FAILED) 512 { 513 break; 514 } 515 // try again! 516 continue; 517 } 518 finally 519 { 520 try 521 { 522 inStream.close(); 523 } 524 catch (final IOException ioe) 525 { 526 // Ignore the exception. 527 inStream = null; 528 } 529 } 530 } 531 532 if (parsed) 533 { 534 parsePendingCatalogs(); 535 } 536 } 537 538 /** 539 * Perform character normalization on a URI reference. 540 * 541 * @param uriref The URI reference 542 * @return The normalized URI reference. 543 */ 544 @Override 545 protected String normalizeURI(final String uriref) 546 { 547 final ConfigurationInterpolator ci = ((CatalogManager) catalogManager).getInterpolator(); 548 final String resolved = ci != null ? String.valueOf(ci.interpolate(uriref)) : uriref; 549 return super.normalizeURI(resolved); 550 } 551 } 552}