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