1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.geronimo.deployment.xml; 19 20 import java.io.BufferedInputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.net.MalformedURLException; 24 import java.net.URI; 25 import java.util.Vector; 26 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29 import org.apache.geronimo.gbean.GBeanInfo; 30 import org.apache.geronimo.gbean.GBeanInfoBuilder; 31 import org.apache.xml.resolver.Catalog; 32 import org.apache.xml.resolver.CatalogEntry; 33 import org.apache.xml.resolver.CatalogException; 34 import org.apache.xml.resolver.CatalogManager; 35 import org.xml.sax.EntityResolver; 36 import org.xml.sax.InputSource; 37 import org.xml.sax.SAXException; 38 39 /** 40 * Implementation of EntityResolver that looks to the local filesystem. 41 * 42 * The implementation tries to resolve an entity via the following steps: 43 * 44 * <ul> 45 * <li>using a catalog file</li> 46 * <li>using a local repository</li> 47 * <li>using JAR files in Classpath</li> 48 * </ul> 49 * 50 * The catalog resolving is based on the OASIS XML Catalog Standard. 51 * OASIS seems to move it around. Try 52 * http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=entity 53 * and the list of documents currently at 54 * http://www.oasis-open.org/committees/documents.php?wg_abbrev=entity 55 * An older version may be at 56 * http://www.oasis-open.org/committees/entity/archives/spec-2001-08-01.html 57 * and see http://www.oasis-open.org/html/a401.htm 58 * 59 * @version $Rev: 653740 $ $Date: 2008-05-06 03:44:18 -0700 (Tue, 06 May 2008) $ 60 */ 61 public class LocalEntityResolver implements EntityResolver { 62 private static final Logger log = LoggerFactory.getLogger(LocalEntityResolver.class); 63 64 /** 65 * The used Catalog Manager 66 */ 67 private final CatalogManager manager = new CatalogManager(); 68 69 /** 70 * the XML Catalog 71 */ 72 private Catalog catalog = null; 73 74 /** 75 * the URI of the catalog file 76 */ 77 private URI catalogFileURI = null; 78 79 /** 80 * Local Repository where DTDs and Schemas are located 81 */ 82 private URI localRepositoryURI = null; 83 84 /** 85 * Flag indicating if this resolver may return null to signal 86 * the parser to open a regular URI connection to the system 87 * identifier. Otherwise an exception is thrown. 88 */ 89 private boolean failOnUnresolvable = false; 90 91 92 public LocalEntityResolver(URI catalogFileURI, URI localRepositoryURI, boolean failOnUnresolvable) { 93 this.catalogFileURI = catalogFileURI; 94 setLocalRepositoryURI(localRepositoryURI); 95 setFailOnUnresolvable(failOnUnresolvable); 96 init(); 97 } 98 99 /** 100 * Sets the setFailOnUnresolvable flag. 101 * 102 * @param b value (true means that a SAXException is thrown 103 * if the entity could not be resolved) 104 */ 105 public void setFailOnUnresolvable(final boolean b) { 106 failOnUnresolvable = b; 107 } 108 109 public boolean isFailOnUnresolvable() { 110 return failOnUnresolvable; 111 } 112 113 public void setCatalogFileURI(final URI catalogFileURI) { 114 this.catalogFileURI = catalogFileURI; 115 init(); 116 } 117 118 public URI getCatalogFileURI() { 119 return this.catalogFileURI; 120 } 121 122 public URI getLocalRepositoryURI() { 123 return localRepositoryURI; 124 } 125 126 public void setLocalRepositoryURI(URI string) { 127 localRepositoryURI = string; 128 } 129 130 public void addPublicMapping(final String publicId, final String uri) { 131 132 Vector args = new Vector(); 133 args.add(publicId); 134 args.add(uri); 135 136 addEntry("PUBLIC", args); 137 138 } 139 140 public void addSystemMapping(final String systemId, final String uri) { 141 142 Vector args = new Vector(); 143 args.add(systemId); 144 args.add(uri); 145 146 addEntry("SYSTEM", args); 147 148 } 149 150 /** 151 * Attempt to resolve the entity based on the supplied publicId and systemId. 152 * First the catalog is queried with both publicId and systemId. 153 * Then the local repository is queried with the file name part of the systemId 154 * Then the classpath is queried with the file name part of the systemId. 155 * 156 * Then, if failOnUnresolvable is true, an exception is thrown: otherwise null is returned. 157 * @param publicId 158 * @param systemId 159 * @return 160 * @throws SAXException 161 * @throws IOException 162 */ 163 public InputSource resolveEntity( 164 final String publicId, 165 final String systemId) 166 throws SAXException, IOException { 167 168 if (log.isTraceEnabled()) { 169 log.trace("start resolving for " + entityMessageString(publicId, systemId)); 170 } 171 172 InputSource source = resolveWithCatalog(publicId, systemId); 173 if (source != null) { 174 return source; 175 } 176 177 source = resolveWithRepository(publicId, systemId); 178 if (source != null) { 179 return source; 180 } 181 182 source = resolveWithClasspath(publicId, systemId); 183 if (source != null) { 184 return source; 185 } 186 187 String message = "could not resolve " + entityMessageString(publicId, systemId); 188 189 if (failOnUnresolvable) { 190 throw new SAXException(message); 191 } else { 192 log.debug(message); 193 } 194 195 return null; 196 } 197 198 /** 199 * Try to resolve using the catalog file 200 * 201 * @param publicId the PublicId 202 * @param systemId the SystemId 203 * @return InputSource if the entity could be resolved. null otherwise 204 * @throws MalformedURLException 205 * @throws IOException 206 */ 207 InputSource resolveWithCatalog( 208 final String publicId, 209 final String systemId) 210 throws MalformedURLException, IOException { 211 212 if (catalogFileURI == null) { 213 return null; 214 } 215 216 String resolvedSystemId = 217 catalog.resolvePublic(guaranteeNotNull(publicId), systemId); 218 219 if (resolvedSystemId != null) { 220 if (log.isTraceEnabled()) { 221 log.trace("resolved " + entityMessageString(publicId, systemId) + " using the catalog file. result: " + resolvedSystemId); 222 } 223 return new InputSource(resolvedSystemId); 224 } 225 226 return null; 227 } 228 229 /** 230 * Try to resolve using the local repository and only the supplied systemID filename. 231 * Any path in the systemID will be removed. 232 * 233 * @param publicId the PublicId 234 * @param systemId the SystemId 235 * @return InputSource if the entity could be resolved. null otherwise 236 */ 237 InputSource resolveWithRepository( 238 final String publicId, 239 final String systemId) { 240 241 if (localRepositoryURI == null) { 242 return null; 243 } 244 245 String fileName = getSystemIdFileName(systemId); 246 247 if (fileName == null) { 248 return null; 249 } 250 251 InputStream inputStream = null; 252 URI resolvedSystemIDURI; 253 try { 254 resolvedSystemIDURI = localRepositoryURI.resolve(fileName); 255 inputStream = resolvedSystemIDURI.toURL().openStream(); 256 } catch (IOException e) { 257 return null; 258 } catch (IllegalArgumentException e) { 259 //typically "uri is not absolute" 260 return null; 261 } 262 if (inputStream != null) { 263 if (log.isTraceEnabled()) { 264 log.trace("resolved " + entityMessageString(publicId, systemId) + "with file relative to " + localRepositoryURI + resolvedSystemIDURI); 265 } 266 return new InputSource(inputStream); 267 } else { 268 return null; 269 } 270 /* 271 String resolvedSystemId = null; 272 273 File file = new File(localRepositoryURI, fileName); 274 if (file.exists()) { 275 resolvedSystemId = file.getAbsolutePath(); 276 if (log.isTraceEnabled()) { 277 log.trace( 278 "resolved " 279 + entityMessageString(publicId, systemId) 280 + "with file relative to " 281 + localRepositoryURI 282 + resolvedSystemId); 283 } 284 return new InputSource(resolvedSystemId); 285 } 286 return null; 287 */ 288 } 289 290 /** 291 * Try to resolve using the the classpath and only the supplied systemID. 292 * Any path in the systemID will be removed. 293 * 294 * @param publicId the PublicId 295 * @param systemId the SystemId 296 * @return InputSource if the entity could be resolved. null otherwise 297 */ 298 InputSource resolveWithClasspath( 299 final String publicId, 300 final String systemId) { 301 302 String fileName = getSystemIdFileName(systemId); 303 304 if (fileName == null) { 305 return null; 306 } 307 308 InputStream in = 309 getClass().getClassLoader().getResourceAsStream(fileName); 310 if (in != null) { 311 if (log.isTraceEnabled()) { 312 log.trace("resolved " + entityMessageString(publicId, systemId) + " via file found file on classpath: " + fileName); 313 } 314 InputSource is = new InputSource(new BufferedInputStream(in)); 315 is.setSystemId(systemId); 316 return is; 317 } 318 319 return null; 320 } 321 322 /** 323 * Guarantees a not null value 324 */ 325 private String guaranteeNotNull(final String string) { 326 return string != null ? string : ""; 327 } 328 329 /** 330 * Returns the SystemIds filename 331 * 332 * @param systemId SystemId 333 * @return filename 334 */ 335 private String getSystemIdFileName(final String systemId) { 336 337 if (systemId == null) { 338 return null; 339 } 340 341 int indexBackSlash = systemId.lastIndexOf("\\"); 342 int indexSlash = systemId.lastIndexOf("/"); 343 344 int index = Math.max(indexBackSlash, indexSlash); 345 346 String fileName = systemId.substring(index + 1); 347 return fileName; 348 } 349 350 /** 351 * Constructs a debugging message string 352 */ 353 private String entityMessageString( 354 final String publicId, 355 final String systemId) { 356 357 StringBuffer buffer = new StringBuffer("entity with publicId '"); 358 buffer.append(publicId); 359 buffer.append("' and systemId '"); 360 buffer.append(systemId); 361 buffer.append("'"); 362 return buffer.toString(); 363 364 } 365 366 /** 367 * Adds a new Entry to the catalog 368 */ 369 private void addEntry(String type, Vector args) { 370 try { 371 CatalogEntry entry = new CatalogEntry(type, args); 372 catalog.addEntry(entry); 373 } catch (CatalogException e) { 374 throw new RuntimeException(e); 375 } 376 } 377 378 /** 379 * Loads mappings from configuration file 380 */ 381 private void init() { 382 383 if (log.isDebugEnabled()) { 384 log.debug("init catalog file " + this.catalogFileURI); 385 } 386 387 manager.setUseStaticCatalog(false); 388 manager.setCatalogFiles(this.catalogFileURI.toString()); 389 manager.setIgnoreMissingProperties(true); 390 catalog = manager.getCatalog(); 391 } 392 393 public static final GBeanInfo GBEAN_INFO; 394 395 static { 396 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("configurable local entity resolver", LocalEntityResolver.class); 397 398 infoFactory.addAttribute("catalogFileURI", URI.class, true); 399 infoFactory.addAttribute("localRepositoryURI", URI.class, true); 400 infoFactory.addAttribute("failOnUnresolvable", boolean.class, true); 401 402 infoFactory.addOperation("resolveEntity", new Class[]{String.class, String.class}); 403 infoFactory.addOperation("addPublicMapping", new Class[]{String.class, String.class}); 404 infoFactory.addOperation("addSystemMapping", new Class[]{String.class, String.class}); 405 406 infoFactory.setConstructor(new String[]{"catalogFileURI", "localRepositoryURI", "failOnUnresolvable"}); 407 408 GBEAN_INFO = infoFactory.getBeanInfo(); 409 } 410 411 public static GBeanInfo getGBeanInfo() { 412 return GBEAN_INFO; 413 } 414 415 }