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