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 1735895 2016-03-20 18:40:47Z oheger $ 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(String catalogs) 101 { 102 manager.setCatalogFiles(catalogs); 103 } 104 105 /** 106 * Set the FileSystem. 107 * @param fileSystem The FileSystem. 108 */ 109 public void setFileSystem(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(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(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(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(String publicId, String systemId) 176 throws SAXException 177 { 178 String resolved = getResolver().getResolvedEntity(publicId, systemId); 179 180 if (resolved != null) 181 { 182 String badFilePrefix = "file://"; 183 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 URL url = locate(fs, null, resolved); 194 if (url == null) 195 { 196 throw new ConfigurationException("Could not locate " 197 + resolved); 198 } 199 InputStream is = fs.getInputStream(url); 200 InputSource iSource = new InputSource(resolved); 201 iSource.setPublicId(publicId); 202 iSource.setByteStream(is); 203 return iSource; 204 } 205 catch (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(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(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(FileSystem fs, String basePath, String name) 269 { 270 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(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(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(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 (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 String base = ((CatalogManager) catalogManager).getBaseDir(); 413 414 // This is safe because the catalog manager returns a vector of strings. 415 @SuppressWarnings("unchecked") 416 Vector<String> catalogs = catalogManager.getCatalogFiles(); 417 if (catalogs != null) 418 { 419 for (int count = 0; count < catalogs.size(); count++) 420 { 421 String fileName = catalogs.elementAt(count); 422 423 URL url = null; 424 InputStream is = null; 425 426 try 427 { 428 url = locate(fs, base, fileName); 429 if (url != null) 430 { 431 is = fs.getInputStream(url); 432 } 433 } 434 catch (ConfigurationException ce) 435 { 436 String name = url.toString(); 437 // Ignore the exception. 438 catalogManager.debug.message(DEBUG_ALL, 439 "Unable to get input stream for " + name + ". " + ce.getMessage()); 440 } 441 if (is != null) 442 { 443 String mimeType = fileNameMap.getContentTypeFor(fileName); 444 try 445 { 446 if (mimeType != null) 447 { 448 parseCatalog(mimeType, is); 449 continue; 450 } 451 } 452 catch (Exception ex) 453 { 454 // Ignore the exception. 455 catalogManager.debug.message(DEBUG_ALL, 456 "Exception caught parsing input stream for " + fileName + ". " 457 + ex.getMessage()); 458 } 459 finally 460 { 461 is.close(); 462 } 463 } 464 parseCatalog(base, fileName); 465 } 466 } 467 468 } 469 470 /** 471 * Parse the specified catalog file. 472 * @param baseDir The base directory, if not included in the file name. 473 * @param fileName The catalog file. May be a full URI String. 474 * @throws IOException If an error occurs. 475 */ 476 public void parseCatalog(String baseDir, String fileName) throws IOException 477 { 478 base = locate(fs, baseDir, fileName); 479 catalogCwd = base; 480 default_override = catalogManager.getPreferPublic(); 481 catalogManager.debug.message(DEBUG_NORMAL, "Parse catalog: " + fileName); 482 483 boolean parsed = false; 484 485 for (int count = 0; !parsed && count < readerArr.size(); count++) 486 { 487 CatalogReader reader = (CatalogReader) readerArr.get(count); 488 InputStream inStream; 489 490 try 491 { 492 inStream = fs.getInputStream(base); 493 } 494 catch (Exception ex) 495 { 496 catalogManager.debug.message(DEBUG_NORMAL, "Unable to access " + base 497 + ex.getMessage()); 498 break; 499 } 500 501 try 502 { 503 reader.readCatalog(this, inStream); 504 parsed = true; 505 } 506 catch (CatalogException ce) 507 { 508 catalogManager.debug.message(DEBUG_NORMAL, "Parse failed for " + fileName 509 + ce.getMessage()); 510 if (ce.getExceptionType() == CatalogException.PARSE_FAILED) 511 { 512 break; 513 } 514 else 515 { 516 // try again! 517 continue; 518 } 519 } 520 finally 521 { 522 try 523 { 524 inStream.close(); 525 } 526 catch (IOException ioe) 527 { 528 // Ignore the exception. 529 inStream = null; 530 } 531 } 532 } 533 534 if (parsed) 535 { 536 parsePendingCatalogs(); 537 } 538 } 539 540 /** 541 * Perform character normalization on a URI reference. 542 * 543 * @param uriref The URI reference 544 * @return The normalized URI reference. 545 */ 546 @Override 547 protected String normalizeURI(String uriref) 548 { 549 ConfigurationInterpolator ci = ((CatalogManager) catalogManager).getInterpolator(); 550 String resolved = (ci != null) ? String.valueOf(ci.interpolate(uriref)) : uriref; 551 return super.normalizeURI(resolved); 552 } 553 } 554}