001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.beans.factory.xml; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.HashSet; 022import java.util.Set; 023import javax.xml.parsers.ParserConfigurationException; 024 025import org.w3c.dom.Document; 026import org.xml.sax.EntityResolver; 027import org.xml.sax.ErrorHandler; 028import org.xml.sax.InputSource; 029import org.xml.sax.SAXException; 030import org.xml.sax.SAXParseException; 031 032import org.springframework.beans.BeanUtils; 033import org.springframework.beans.factory.BeanDefinitionStoreException; 034import org.springframework.beans.factory.parsing.EmptyReaderEventListener; 035import org.springframework.beans.factory.parsing.FailFastProblemReporter; 036import org.springframework.beans.factory.parsing.NullSourceExtractor; 037import org.springframework.beans.factory.parsing.ProblemReporter; 038import org.springframework.beans.factory.parsing.ReaderEventListener; 039import org.springframework.beans.factory.parsing.SourceExtractor; 040import org.springframework.beans.factory.support.AbstractBeanDefinitionReader; 041import org.springframework.beans.factory.support.BeanDefinitionRegistry; 042import org.springframework.core.Constants; 043import org.springframework.core.NamedThreadLocal; 044import org.springframework.core.io.DescriptiveResource; 045import org.springframework.core.io.Resource; 046import org.springframework.core.io.ResourceLoader; 047import org.springframework.core.io.support.EncodedResource; 048import org.springframework.util.Assert; 049import org.springframework.util.xml.SimpleSaxErrorHandler; 050import org.springframework.util.xml.XmlValidationModeDetector; 051 052/** 053 * Bean definition reader for XML bean definitions. 054 * Delegates the actual XML document reading to an implementation 055 * of the {@link BeanDefinitionDocumentReader} interface. 056 * 057 * <p>Typically applied to a 058 * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} 059 * or a {@link org.springframework.context.support.GenericApplicationContext}. 060 * 061 * <p>This class loads a DOM document and applies the BeanDefinitionDocumentReader to it. 062 * The document reader will register each bean definition with the given bean factory, 063 * talking to the latter's implementation of the 064 * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} interface. 065 * 066 * @author Juergen Hoeller 067 * @author Rob Harrop 068 * @author Chris Beams 069 * @since 26.11.2003 070 * @see #setDocumentReaderClass 071 * @see BeanDefinitionDocumentReader 072 * @see DefaultBeanDefinitionDocumentReader 073 * @see BeanDefinitionRegistry 074 * @see org.springframework.beans.factory.support.DefaultListableBeanFactory 075 * @see org.springframework.context.support.GenericApplicationContext 076 */ 077public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader { 078 079 /** 080 * Indicates that the validation should be disabled. 081 */ 082 public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE; 083 084 /** 085 * Indicates that the validation mode should be detected automatically. 086 */ 087 public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO; 088 089 /** 090 * Indicates that DTD validation should be used. 091 */ 092 public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD; 093 094 /** 095 * Indicates that XSD validation should be used. 096 */ 097 public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD; 098 099 100 /** Constants instance for this class */ 101 private static final Constants constants = new Constants(XmlBeanDefinitionReader.class); 102 103 private int validationMode = VALIDATION_AUTO; 104 105 private boolean namespaceAware = false; 106 107 private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class; 108 109 private ProblemReporter problemReporter = new FailFastProblemReporter(); 110 111 private ReaderEventListener eventListener = new EmptyReaderEventListener(); 112 113 private SourceExtractor sourceExtractor = new NullSourceExtractor(); 114 115 private NamespaceHandlerResolver namespaceHandlerResolver; 116 117 private DocumentLoader documentLoader = new DefaultDocumentLoader(); 118 119 private EntityResolver entityResolver; 120 121 private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger); 122 123 private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector(); 124 125 private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = 126 new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded"); 127 128 129 /** 130 * Create new XmlBeanDefinitionReader for the given bean factory. 131 * @param registry the BeanFactory to load bean definitions into, 132 * in the form of a BeanDefinitionRegistry 133 */ 134 public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { 135 super(registry); 136 } 137 138 139 /** 140 * Set whether to use XML validation. Default is {@code true}. 141 * <p>This method switches namespace awareness on if validation is turned off, 142 * in order to still process schema namespaces properly in such a scenario. 143 * @see #setValidationMode 144 * @see #setNamespaceAware 145 */ 146 public void setValidating(boolean validating) { 147 this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE); 148 this.namespaceAware = !validating; 149 } 150 151 /** 152 * Set the validation mode to use by name. Defaults to {@link #VALIDATION_AUTO}. 153 * @see #setValidationMode 154 */ 155 public void setValidationModeName(String validationModeName) { 156 setValidationMode(constants.asNumber(validationModeName).intValue()); 157 } 158 159 /** 160 * Set the validation mode to use. Defaults to {@link #VALIDATION_AUTO}. 161 * <p>Note that this only activates or deactivates validation itself. 162 * If you are switching validation off for schema files, you might need to 163 * activate schema namespace support explicitly: see {@link #setNamespaceAware}. 164 */ 165 public void setValidationMode(int validationMode) { 166 this.validationMode = validationMode; 167 } 168 169 /** 170 * Return the validation mode to use. 171 */ 172 public int getValidationMode() { 173 return this.validationMode; 174 } 175 176 /** 177 * Set whether or not the XML parser should be XML namespace aware. 178 * Default is "false". 179 * <p>This is typically not needed when schema validation is active. 180 * However, without validation, this has to be switched to "true" 181 * in order to properly process schema namespaces. 182 */ 183 public void setNamespaceAware(boolean namespaceAware) { 184 this.namespaceAware = namespaceAware; 185 } 186 187 /** 188 * Return whether or not the XML parser should be XML namespace aware. 189 */ 190 public boolean isNamespaceAware() { 191 return this.namespaceAware; 192 } 193 194 /** 195 * Specify which {@link org.springframework.beans.factory.parsing.ProblemReporter} to use. 196 * <p>The default implementation is {@link org.springframework.beans.factory.parsing.FailFastProblemReporter} 197 * which exhibits fail fast behaviour. External tools can provide an alternative implementation 198 * that collates errors and warnings for display in the tool UI. 199 */ 200 public void setProblemReporter(ProblemReporter problemReporter) { 201 this.problemReporter = (problemReporter != null ? problemReporter : new FailFastProblemReporter()); 202 } 203 204 /** 205 * Specify which {@link ReaderEventListener} to use. 206 * <p>The default implementation is EmptyReaderEventListener which discards every event notification. 207 * External tools can provide an alternative implementation to monitor the components being 208 * registered in the BeanFactory. 209 */ 210 public void setEventListener(ReaderEventListener eventListener) { 211 this.eventListener = (eventListener != null ? eventListener : new EmptyReaderEventListener()); 212 } 213 214 /** 215 * Specify the {@link SourceExtractor} to use. 216 * <p>The default implementation is {@link NullSourceExtractor} which simply returns {@code null} 217 * as the source object. This means that - during normal runtime execution - 218 * no additional source metadata is attached to the bean configuration metadata. 219 */ 220 public void setSourceExtractor(SourceExtractor sourceExtractor) { 221 this.sourceExtractor = (sourceExtractor != null ? sourceExtractor : new NullSourceExtractor()); 222 } 223 224 /** 225 * Specify the {@link NamespaceHandlerResolver} to use. 226 * <p>If none is specified, a default instance will be created through 227 * {@link #createDefaultNamespaceHandlerResolver()}. 228 */ 229 public void setNamespaceHandlerResolver(NamespaceHandlerResolver namespaceHandlerResolver) { 230 this.namespaceHandlerResolver = namespaceHandlerResolver; 231 } 232 233 /** 234 * Specify the {@link DocumentLoader} to use. 235 * <p>The default implementation is {@link DefaultDocumentLoader} 236 * which loads {@link Document} instances using JAXP. 237 */ 238 public void setDocumentLoader(DocumentLoader documentLoader) { 239 this.documentLoader = (documentLoader != null ? documentLoader : new DefaultDocumentLoader()); 240 } 241 242 /** 243 * Set a SAX entity resolver to be used for parsing. 244 * <p>By default, {@link ResourceEntityResolver} will be used. Can be overridden 245 * for custom entity resolution, for example relative to some specific base path. 246 */ 247 public void setEntityResolver(EntityResolver entityResolver) { 248 this.entityResolver = entityResolver; 249 } 250 251 /** 252 * Return the EntityResolver to use, building a default resolver 253 * if none specified. 254 */ 255 protected EntityResolver getEntityResolver() { 256 if (this.entityResolver == null) { 257 // Determine default EntityResolver to use. 258 ResourceLoader resourceLoader = getResourceLoader(); 259 if (resourceLoader != null) { 260 this.entityResolver = new ResourceEntityResolver(resourceLoader); 261 } 262 else { 263 this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); 264 } 265 } 266 return this.entityResolver; 267 } 268 269 /** 270 * Set an implementation of the {@code org.xml.sax.ErrorHandler} 271 * interface for custom handling of XML parsing errors and warnings. 272 * <p>If not set, a default SimpleSaxErrorHandler is used that simply 273 * logs warnings using the logger instance of the view class, 274 * and rethrows errors to discontinue the XML transformation. 275 * @see SimpleSaxErrorHandler 276 */ 277 public void setErrorHandler(ErrorHandler errorHandler) { 278 this.errorHandler = errorHandler; 279 } 280 281 /** 282 * Specify the {@link BeanDefinitionDocumentReader} implementation to use, 283 * responsible for the actual reading of the XML bean definition document. 284 * <p>The default is {@link DefaultBeanDefinitionDocumentReader}. 285 * @param documentReaderClass the desired BeanDefinitionDocumentReader implementation class 286 */ 287 public void setDocumentReaderClass(Class<?> documentReaderClass) { 288 if (documentReaderClass == null || !BeanDefinitionDocumentReader.class.isAssignableFrom(documentReaderClass)) { 289 throw new IllegalArgumentException( 290 "documentReaderClass must be an implementation of the BeanDefinitionDocumentReader interface"); 291 } 292 this.documentReaderClass = documentReaderClass; 293 } 294 295 296 /** 297 * Load bean definitions from the specified XML file. 298 * @param resource the resource descriptor for the XML file 299 * @return the number of bean definitions found 300 * @throws BeanDefinitionStoreException in case of loading or parsing errors 301 */ 302 @Override 303 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { 304 return loadBeanDefinitions(new EncodedResource(resource)); 305 } 306 307 /** 308 * Load bean definitions from the specified XML file. 309 * @param encodedResource the resource descriptor for the XML file, 310 * allowing to specify an encoding to use for parsing the file 311 * @return the number of bean definitions found 312 * @throws BeanDefinitionStoreException in case of loading or parsing errors 313 */ 314 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 315 Assert.notNull(encodedResource, "EncodedResource must not be null"); 316 if (logger.isInfoEnabled()) { 317 logger.info("Loading XML bean definitions from " + encodedResource); 318 } 319 320 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); 321 if (currentResources == null) { 322 currentResources = new HashSet<EncodedResource>(4); 323 this.resourcesCurrentlyBeingLoaded.set(currentResources); 324 } 325 if (!currentResources.add(encodedResource)) { 326 throw new BeanDefinitionStoreException( 327 "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); 328 } 329 try { 330 InputStream inputStream = encodedResource.getResource().getInputStream(); 331 try { 332 InputSource inputSource = new InputSource(inputStream); 333 if (encodedResource.getEncoding() != null) { 334 inputSource.setEncoding(encodedResource.getEncoding()); 335 } 336 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 337 } 338 finally { 339 inputStream.close(); 340 } 341 } 342 catch (IOException ex) { 343 throw new BeanDefinitionStoreException( 344 "IOException parsing XML document from " + encodedResource.getResource(), ex); 345 } 346 finally { 347 currentResources.remove(encodedResource); 348 if (currentResources.isEmpty()) { 349 this.resourcesCurrentlyBeingLoaded.remove(); 350 } 351 } 352 } 353 354 /** 355 * Load bean definitions from the specified XML file. 356 * @param inputSource the SAX InputSource to read from 357 * @return the number of bean definitions found 358 * @throws BeanDefinitionStoreException in case of loading or parsing errors 359 */ 360 public int loadBeanDefinitions(InputSource inputSource) throws BeanDefinitionStoreException { 361 return loadBeanDefinitions(inputSource, "resource loaded through SAX InputSource"); 362 } 363 364 /** 365 * Load bean definitions from the specified XML file. 366 * @param inputSource the SAX InputSource to read from 367 * @param resourceDescription a description of the resource 368 * (can be {@code null} or empty) 369 * @return the number of bean definitions found 370 * @throws BeanDefinitionStoreException in case of loading or parsing errors 371 */ 372 public int loadBeanDefinitions(InputSource inputSource, String resourceDescription) 373 throws BeanDefinitionStoreException { 374 375 return doLoadBeanDefinitions(inputSource, new DescriptiveResource(resourceDescription)); 376 } 377 378 379 /** 380 * Actually load bean definitions from the specified XML file. 381 * @param inputSource the SAX InputSource to read from 382 * @param resource the resource descriptor for the XML file 383 * @return the number of bean definitions found 384 * @throws BeanDefinitionStoreException in case of loading or parsing errors 385 * @see #doLoadDocument 386 * @see #registerBeanDefinitions 387 */ 388 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 389 throws BeanDefinitionStoreException { 390 try { 391 Document doc = doLoadDocument(inputSource, resource); 392 return registerBeanDefinitions(doc, resource); 393 } 394 catch (BeanDefinitionStoreException ex) { 395 throw ex; 396 } 397 catch (SAXParseException ex) { 398 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 399 "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); 400 } 401 catch (SAXException ex) { 402 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 403 "XML document from " + resource + " is invalid", ex); 404 } 405 catch (ParserConfigurationException ex) { 406 throw new BeanDefinitionStoreException(resource.getDescription(), 407 "Parser configuration exception parsing XML from " + resource, ex); 408 } 409 catch (IOException ex) { 410 throw new BeanDefinitionStoreException(resource.getDescription(), 411 "IOException parsing XML document from " + resource, ex); 412 } 413 catch (Throwable ex) { 414 throw new BeanDefinitionStoreException(resource.getDescription(), 415 "Unexpected exception parsing XML document from " + resource, ex); 416 } 417 } 418 419 /** 420 * Actually load the specified document using the configured DocumentLoader. 421 * @param inputSource the SAX InputSource to read from 422 * @param resource the resource descriptor for the XML file 423 * @return the DOM Document 424 * @throws Exception when thrown from the DocumentLoader 425 * @see #setDocumentLoader 426 * @see DocumentLoader#loadDocument 427 */ 428 protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { 429 return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, 430 getValidationModeForResource(resource), isNamespaceAware()); 431 } 432 433 /** 434 * Determine the validation mode for the specified {@link Resource}. 435 * If no explicit validation mode has been configured, then the validation 436 * mode gets {@link #detectValidationMode detected} from the given resource. 437 * <p>Override this method if you would like full control over the validation 438 * mode, even when something other than {@link #VALIDATION_AUTO} was set. 439 * @see #detectValidationMode 440 */ 441 protected int getValidationModeForResource(Resource resource) { 442 int validationModeToUse = getValidationMode(); 443 if (validationModeToUse != VALIDATION_AUTO) { 444 return validationModeToUse; 445 } 446 int detectedMode = detectValidationMode(resource); 447 if (detectedMode != VALIDATION_AUTO) { 448 return detectedMode; 449 } 450 // Hmm, we didn't get a clear indication... Let's assume XSD, 451 // since apparently no DTD declaration has been found up until 452 // detection stopped (before finding the document's root tag). 453 return VALIDATION_XSD; 454 } 455 456 /** 457 * Detect which kind of validation to perform on the XML file identified 458 * by the supplied {@link Resource}. If the file has a {@code DOCTYPE} 459 * definition then DTD validation is used otherwise XSD validation is assumed. 460 * <p>Override this method if you would like to customize resolution 461 * of the {@link #VALIDATION_AUTO} mode. 462 */ 463 protected int detectValidationMode(Resource resource) { 464 if (resource.isOpen()) { 465 throw new BeanDefinitionStoreException( 466 "Passed-in Resource [" + resource + "] contains an open stream: " + 467 "cannot determine validation mode automatically. Either pass in a Resource " + 468 "that is able to create fresh streams, or explicitly specify the validationMode " + 469 "on your XmlBeanDefinitionReader instance."); 470 } 471 472 InputStream inputStream; 473 try { 474 inputStream = resource.getInputStream(); 475 } 476 catch (IOException ex) { 477 throw new BeanDefinitionStoreException( 478 "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " + 479 "Did you attempt to load directly from a SAX InputSource without specifying the " + 480 "validationMode on your XmlBeanDefinitionReader instance?", ex); 481 } 482 483 try { 484 return this.validationModeDetector.detectValidationMode(inputStream); 485 } 486 catch (IOException ex) { 487 throw new BeanDefinitionStoreException("Unable to determine validation mode for [" + 488 resource + "]: an error occurred whilst reading from the InputStream.", ex); 489 } 490 } 491 492 /** 493 * Register the bean definitions contained in the given DOM document. 494 * Called by {@code loadBeanDefinitions}. 495 * <p>Creates a new instance of the parser class and invokes 496 * {@code registerBeanDefinitions} on it. 497 * @param doc the DOM document 498 * @param resource the resource descriptor (for context information) 499 * @return the number of bean definitions found 500 * @throws BeanDefinitionStoreException in case of parsing errors 501 * @see #loadBeanDefinitions 502 * @see #setDocumentReaderClass 503 * @see BeanDefinitionDocumentReader#registerBeanDefinitions 504 */ 505 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 506 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 507 int countBefore = getRegistry().getBeanDefinitionCount(); 508 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 509 return getRegistry().getBeanDefinitionCount() - countBefore; 510 } 511 512 /** 513 * Create the {@link BeanDefinitionDocumentReader} to use for actually 514 * reading bean definitions from an XML document. 515 * <p>The default implementation instantiates the specified "documentReaderClass". 516 * @see #setDocumentReaderClass 517 */ 518 protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { 519 return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass)); 520 } 521 522 /** 523 * Create the {@link XmlReaderContext} to pass over to the document reader. 524 */ 525 public XmlReaderContext createReaderContext(Resource resource) { 526 return new XmlReaderContext(resource, this.problemReporter, this.eventListener, 527 this.sourceExtractor, this, getNamespaceHandlerResolver()); 528 } 529 530 /** 531 * Lazily create a default NamespaceHandlerResolver, if not set before. 532 * @see #createDefaultNamespaceHandlerResolver() 533 */ 534 public NamespaceHandlerResolver getNamespaceHandlerResolver() { 535 if (this.namespaceHandlerResolver == null) { 536 this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); 537 } 538 return this.namespaceHandlerResolver; 539 } 540 541 /** 542 * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified. 543 * <p>The default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}. 544 * @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader) 545 */ 546 protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { 547 return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader()); 548 } 549 550}