001 /** 002 * 003 * Copyright 2003-2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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 018 package org.apache.geronimo.deployment.xml; 019 020 import java.io.BufferedInputStream; 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.net.MalformedURLException; 024 import java.net.URI; 025 import java.util.Vector; 026 027 import org.apache.commons.logging.Log; 028 import org.apache.commons.logging.LogFactory; 029 import org.apache.geronimo.gbean.GBeanInfo; 030 import org.apache.geronimo.gbean.GBeanInfoBuilder; 031 import org.apache.xml.resolver.Catalog; 032 import org.apache.xml.resolver.CatalogEntry; 033 import org.apache.xml.resolver.CatalogException; 034 import org.apache.xml.resolver.CatalogManager; 035 import org.xml.sax.EntityResolver; 036 import org.xml.sax.InputSource; 037 import org.xml.sax.SAXException; 038 039 /** 040 * Implementation of EntityResolver that looks to the local filesystem. 041 * 042 * The implementation tries to resolve an entity via the following steps: 043 * 044 * <ul> 045 * <li>using a catalog file</li> 046 * <li>using a local repository</li> 047 * <li>using JAR files in Classpath</li> 048 * </ul> 049 * 050 * The catalog resolving is based on the OASIS XML Catalog Standard. 051 * OASIS seems to move it around. Try 052 * http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=entity 053 * and the list of documents currently at 054 * http://www.oasis-open.org/committees/documents.php?wg_abbrev=entity 055 * An older version may be at 056 * http://www.oasis-open.org/committees/entity/archives/spec-2001-08-01.html 057 * and see http://www.oasis-open.org/html/a401.htm 058 * 059 * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $ 060 */ 061 public class LocalEntityResolver implements EntityResolver { 062 /** 063 * used Logger 064 */ 065 private static final Log log = LogFactory.getLog(LocalEntityResolver.class); 066 067 /** 068 * The used Catalog Manager 069 */ 070 private final CatalogManager manager = new CatalogManager(); 071 072 /** 073 * the XML Catalog 074 */ 075 private Catalog catalog = null; 076 077 /** 078 * the URI of the catalog file 079 */ 080 private URI catalogFileURI = null; 081 082 /** 083 * Local Repository where DTDs and Schemas are located 084 */ 085 private URI localRepositoryURI = null; 086 087 /** 088 * Flag indicating if this resolver may return null to signal 089 * the parser to open a regular URI connection to the system 090 * identifier. Otherwise an exception is thrown. 091 */ 092 private boolean failOnUnresolvable = false; 093 094 095 public LocalEntityResolver(URI catalogFileURI, URI localRepositoryURI, boolean failOnUnresolvable) { 096 this.catalogFileURI = catalogFileURI; 097 setLocalRepositoryURI(localRepositoryURI); 098 setFailOnUnresolvable(failOnUnresolvable); 099 init(); 100 } 101 102 /** 103 * Sets the setFailOnUnresolvable flag. 104 * 105 * @param b value (true means that a SAXException is thrown 106 * if the entity could not be resolved) 107 */ 108 public void setFailOnUnresolvable(final boolean b) { 109 failOnUnresolvable = b; 110 } 111 112 public boolean isFailOnUnresolvable() { 113 return failOnUnresolvable; 114 } 115 116 public void setCatalogFileURI(final URI catalogFileURI) { 117 this.catalogFileURI = catalogFileURI; 118 init(); 119 } 120 121 public URI getCatalogFileURI() { 122 return this.catalogFileURI; 123 } 124 125 public URI getLocalRepositoryURI() { 126 return localRepositoryURI; 127 } 128 129 public void setLocalRepositoryURI(URI string) { 130 localRepositoryURI = string; 131 } 132 133 public void addPublicMapping(final String publicId, final String uri) { 134 135 Vector args = new Vector(); 136 args.add(publicId); 137 args.add(uri); 138 139 addEntry("PUBLIC", args); 140 141 } 142 143 public void addSystemMapping(final String systemId, final String uri) { 144 145 Vector args = new Vector(); 146 args.add(systemId); 147 args.add(uri); 148 149 addEntry("SYSTEM", args); 150 151 } 152 153 /** 154 * Attempt to resolve the entity based on the supplied publicId and systemId. 155 * First the catalog is queried with both publicId and systemId. 156 * Then the local repository is queried with the file name part of the systemId 157 * Then the classpath is queried with the file name part of the systemId. 158 * 159 * Then, if failOnUnresolvable is true, an exception is thrown: otherwise null is returned. 160 * @param publicId 161 * @param systemId 162 * @return 163 * @throws SAXException 164 * @throws IOException 165 */ 166 public InputSource resolveEntity( 167 final String publicId, 168 final String systemId) 169 throws SAXException, IOException { 170 171 if (log.isTraceEnabled()) { 172 log.trace("start resolving for " + entityMessageString(publicId, systemId)); 173 } 174 175 InputSource source = resolveWithCatalog(publicId, systemId); 176 if (source != null) { 177 return source; 178 } 179 180 source = resolveWithRepository(publicId, systemId); 181 if (source != null) { 182 return source; 183 } 184 185 source = resolveWithClasspath(publicId, systemId); 186 if (source != null) { 187 return source; 188 } 189 190 String message = "could not resolve " + entityMessageString(publicId, systemId); 191 192 if (failOnUnresolvable) { 193 throw new SAXException(message); 194 } else { 195 log.debug(message); 196 } 197 198 return null; 199 } 200 201 /** 202 * Try to resolve using the catalog file 203 * 204 * @param publicId the PublicId 205 * @param systemId the SystemId 206 * @return InputSource if the entity could be resolved. null otherwise 207 * @throws MalformedURLException 208 * @throws IOException 209 */ 210 InputSource resolveWithCatalog( 211 final String publicId, 212 final String systemId) 213 throws MalformedURLException, IOException { 214 215 if (catalogFileURI == null) { 216 return null; 217 } 218 219 String resolvedSystemId = 220 catalog.resolvePublic(guaranteeNotNull(publicId), systemId); 221 222 if (resolvedSystemId != null) { 223 if (log.isTraceEnabled()) { 224 log.trace("resolved " + entityMessageString(publicId, systemId) + " using the catalog file. result: " + resolvedSystemId); 225 } 226 return new InputSource(resolvedSystemId); 227 } 228 229 return null; 230 } 231 232 /** 233 * Try to resolve using the local repository and only the supplied systemID filename. 234 * Any path in the systemID will be removed. 235 * 236 * @param publicId the PublicId 237 * @param systemId the SystemId 238 * @return InputSource if the entity could be resolved. null otherwise 239 */ 240 InputSource resolveWithRepository( 241 final String publicId, 242 final String systemId) { 243 244 if (localRepositoryURI == null) { 245 return null; 246 } 247 248 String fileName = getSystemIdFileName(systemId); 249 250 if (fileName == null) { 251 return null; 252 } 253 254 InputStream inputStream = null; 255 URI resolvedSystemIDURI; 256 try { 257 resolvedSystemIDURI = localRepositoryURI.resolve(fileName); 258 inputStream = resolvedSystemIDURI.toURL().openStream(); 259 } catch (IOException e) { 260 return null; 261 } catch (IllegalArgumentException e) { 262 //typically "uri is not absolute" 263 return null; 264 } 265 if (inputStream != null) { 266 if (log.isTraceEnabled()) { 267 log.trace("resolved " + entityMessageString(publicId, systemId) + "with file relative to " + localRepositoryURI + resolvedSystemIDURI); 268 } 269 return new InputSource(inputStream); 270 } else { 271 return null; 272 } 273 /* 274 String resolvedSystemId = null; 275 276 File file = new File(localRepositoryURI, fileName); 277 if (file.exists()) { 278 resolvedSystemId = file.getAbsolutePath(); 279 if (log.isTraceEnabled()) { 280 log.trace( 281 "resolved " 282 + entityMessageString(publicId, systemId) 283 + "with file relative to " 284 + localRepositoryURI 285 + resolvedSystemId); 286 } 287 return new InputSource(resolvedSystemId); 288 } 289 return null; 290 */ 291 } 292 293 /** 294 * Try to resolve using the the classpath and only the supplied systemID. 295 * Any path in the systemID will be removed. 296 * 297 * @param publicId the PublicId 298 * @param systemId the SystemId 299 * @return InputSource if the entity could be resolved. null otherwise 300 */ 301 InputSource resolveWithClasspath( 302 final String publicId, 303 final String systemId) { 304 305 String fileName = getSystemIdFileName(systemId); 306 307 if (fileName == null) { 308 return null; 309 } 310 311 InputStream in = 312 getClass().getClassLoader().getResourceAsStream(fileName); 313 if (in != null) { 314 if (log.isTraceEnabled()) { 315 log.trace("resolved " + entityMessageString(publicId, systemId) + " via file found file on classpath: " + fileName); 316 } 317 InputSource is = new InputSource(new BufferedInputStream(in)); 318 is.setSystemId(systemId); 319 return is; 320 } 321 322 return null; 323 } 324 325 /** 326 * Guarantees a not null value 327 */ 328 private String guaranteeNotNull(final String string) { 329 return string != null ? string : ""; 330 } 331 332 /** 333 * Returns the SystemIds filename 334 * 335 * @param systemId SystemId 336 * @return filename 337 */ 338 private String getSystemIdFileName(final String systemId) { 339 340 if (systemId == null) { 341 return null; 342 } 343 344 int indexBackSlash = systemId.lastIndexOf("\\"); 345 int indexSlash = systemId.lastIndexOf("/"); 346 347 int index = Math.max(indexBackSlash, indexSlash); 348 349 String fileName = systemId.substring(index + 1); 350 return fileName; 351 } 352 353 /** 354 * Constructs a debugging message string 355 */ 356 private String entityMessageString( 357 final String publicId, 358 final String systemId) { 359 360 StringBuffer buffer = new StringBuffer("entity with publicId '"); 361 buffer.append(publicId); 362 buffer.append("' and systemId '"); 363 buffer.append(systemId); 364 buffer.append("'"); 365 return buffer.toString(); 366 367 } 368 369 /** 370 * Adds a new Entry to the catalog 371 */ 372 private void addEntry(String type, Vector args) { 373 try { 374 CatalogEntry entry = new CatalogEntry(type, args); 375 catalog.addEntry(entry); 376 } catch (CatalogException e) { 377 throw new RuntimeException(e); 378 } 379 } 380 381 /** 382 * Loads mappings from configuration file 383 */ 384 private void init() { 385 386 if (log.isDebugEnabled()) { 387 log.debug("init catalog file " + this.catalogFileURI); 388 } 389 390 manager.setUseStaticCatalog(false); 391 manager.setCatalogFiles(this.catalogFileURI.toString()); 392 manager.setIgnoreMissingProperties(true); 393 catalog = manager.getCatalog(); 394 } 395 396 public static final GBeanInfo GBEAN_INFO; 397 398 static { 399 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("configurable local entity resolver", LocalEntityResolver.class); 400 401 infoFactory.addAttribute("catalogFileURI", URI.class, true); 402 infoFactory.addAttribute("localRepositoryURI", URI.class, true); 403 infoFactory.addAttribute("failOnUnresolvable", boolean.class, true); 404 405 infoFactory.addOperation("resolveEntity", new Class[]{String.class, String.class}); 406 infoFactory.addOperation("addPublicMapping", new Class[]{String.class, String.class}); 407 infoFactory.addOperation("addSystemMapping", new Class[]{String.class, String.class}); 408 409 infoFactory.setConstructor(new String[]{"catalogFileURI", "localRepositoryURI", "failOnUnresolvable"}); 410 411 GBEAN_INFO = infoFactory.getBeanInfo(); 412 } 413 414 public static GBeanInfo getGBeanInfo() { 415 return GBEAN_INFO; 416 } 417 418 }