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.net.URISyntaxException; 021import java.util.LinkedHashSet; 022import java.util.Set; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.w3c.dom.Document; 027import org.w3c.dom.Element; 028import org.w3c.dom.Node; 029import org.w3c.dom.NodeList; 030 031import org.springframework.beans.factory.BeanDefinitionStoreException; 032import org.springframework.beans.factory.config.BeanDefinitionHolder; 033import org.springframework.beans.factory.parsing.BeanComponentDefinition; 034import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; 035import org.springframework.core.io.Resource; 036import org.springframework.core.io.support.ResourcePatternUtils; 037import org.springframework.lang.Nullable; 038import org.springframework.util.Assert; 039import org.springframework.util.ResourceUtils; 040import org.springframework.util.StringUtils; 041 042/** 043 * Default implementation of the {@link BeanDefinitionDocumentReader} interface that 044 * reads bean definitions according to the "spring-beans" DTD and XSD format 045 * (Spring's default XML bean definition format). 046 * 047 * <p>The structure, elements, and attribute names of the required XML document 048 * are hard-coded in this class. (Of course a transform could be run if necessary 049 * to produce this format). {@code <beans>} does not need to be the root 050 * element of the XML document: this class will parse all bean definition elements 051 * in the XML file, regardless of the actual root element. 052 * 053 * @author Rod Johnson 054 * @author Juergen Hoeller 055 * @author Rob Harrop 056 * @author Erik Wiersma 057 * @since 18.12.2003 058 */ 059public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader { 060 061 public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT; 062 063 public static final String NESTED_BEANS_ELEMENT = "beans"; 064 065 public static final String ALIAS_ELEMENT = "alias"; 066 067 public static final String NAME_ATTRIBUTE = "name"; 068 069 public static final String ALIAS_ATTRIBUTE = "alias"; 070 071 public static final String IMPORT_ELEMENT = "import"; 072 073 public static final String RESOURCE_ATTRIBUTE = "resource"; 074 075 public static final String PROFILE_ATTRIBUTE = "profile"; 076 077 078 protected final Log logger = LogFactory.getLog(getClass()); 079 080 @Nullable 081 private XmlReaderContext readerContext; 082 083 @Nullable 084 private BeanDefinitionParserDelegate delegate; 085 086 087 /** 088 * This implementation parses bean definitions according to the "spring-beans" XSD 089 * (or DTD, historically). 090 * <p>Opens a DOM Document; then initializes the default settings 091 * specified at the {@code <beans/>} level; then parses the contained bean definitions. 092 */ 093 @Override 094 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { 095 this.readerContext = readerContext; 096 doRegisterBeanDefinitions(doc.getDocumentElement()); 097 } 098 099 /** 100 * Return the descriptor for the XML resource that this parser works on. 101 */ 102 protected final XmlReaderContext getReaderContext() { 103 Assert.state(this.readerContext != null, "No XmlReaderContext available"); 104 return this.readerContext; 105 } 106 107 /** 108 * Invoke the {@link org.springframework.beans.factory.parsing.SourceExtractor} 109 * to pull the source metadata from the supplied {@link Element}. 110 */ 111 @Nullable 112 protected Object extractSource(Element ele) { 113 return getReaderContext().extractSource(ele); 114 } 115 116 117 /** 118 * Register each bean definition within the given root {@code <beans/>} element. 119 */ 120 @SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...) 121 protected void doRegisterBeanDefinitions(Element root) { 122 // Any nested <beans> elements will cause recursion in this method. In 123 // order to propagate and preserve <beans> default-* attributes correctly, 124 // keep track of the current (parent) delegate, which may be null. Create 125 // the new (child) delegate with a reference to the parent for fallback purposes, 126 // then ultimately reset this.delegate back to its original (parent) reference. 127 // this behavior emulates a stack of delegates without actually necessitating one. 128 BeanDefinitionParserDelegate parent = this.delegate; 129 this.delegate = createDelegate(getReaderContext(), root, parent); 130 131 if (this.delegate.isDefaultNamespace(root)) { 132 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); 133 if (StringUtils.hasText(profileSpec)) { 134 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( 135 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); 136 // We cannot use Profiles.of(...) since profile expressions are not supported 137 // in XML config. See SPR-12458 for details. 138 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { 139 if (logger.isDebugEnabled()) { 140 logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + 141 "] not matching: " + getReaderContext().getResource()); 142 } 143 return; 144 } 145 } 146 } 147 148 preProcessXml(root); 149 parseBeanDefinitions(root, this.delegate); 150 postProcessXml(root); 151 152 this.delegate = parent; 153 } 154 155 protected BeanDefinitionParserDelegate createDelegate( 156 XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) { 157 158 BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); 159 delegate.initDefaults(root, parentDelegate); 160 return delegate; 161 } 162 163 /** 164 * Parse the elements at the root level in the document: 165 * "import", "alias", "bean". 166 * @param root the DOM root element of the document 167 */ 168 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 169 if (delegate.isDefaultNamespace(root)) { 170 NodeList nl = root.getChildNodes(); 171 for (int i = 0; i < nl.getLength(); i++) { 172 Node node = nl.item(i); 173 if (node instanceof Element) { 174 Element ele = (Element) node; 175 if (delegate.isDefaultNamespace(ele)) { 176 parseDefaultElement(ele, delegate); 177 } 178 else { 179 delegate.parseCustomElement(ele); 180 } 181 } 182 } 183 } 184 else { 185 delegate.parseCustomElement(root); 186 } 187 } 188 189 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { 190 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { 191 importBeanDefinitionResource(ele); 192 } 193 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { 194 processAliasRegistration(ele); 195 } 196 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { 197 processBeanDefinition(ele, delegate); 198 } 199 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { 200 // recurse 201 doRegisterBeanDefinitions(ele); 202 } 203 } 204 205 /** 206 * Parse an "import" element and load the bean definitions 207 * from the given resource into the bean factory. 208 */ 209 protected void importBeanDefinitionResource(Element ele) { 210 String location = ele.getAttribute(RESOURCE_ATTRIBUTE); 211 if (!StringUtils.hasText(location)) { 212 getReaderContext().error("Resource location must not be empty", ele); 213 return; 214 } 215 216 // Resolve system properties: e.g. "${user.dir}" 217 location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); 218 219 Set<Resource> actualResources = new LinkedHashSet<>(4); 220 221 // Discover whether the location is an absolute or relative URI 222 boolean absoluteLocation = false; 223 try { 224 absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); 225 } 226 catch (URISyntaxException ex) { 227 // cannot convert to an URI, considering the location relative 228 // unless it is the well-known Spring prefix "classpath*:" 229 } 230 231 // Absolute or relative? 232 if (absoluteLocation) { 233 try { 234 int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); 235 if (logger.isTraceEnabled()) { 236 logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]"); 237 } 238 } 239 catch (BeanDefinitionStoreException ex) { 240 getReaderContext().error( 241 "Failed to import bean definitions from URL location [" + location + "]", ele, ex); 242 } 243 } 244 else { 245 // No URL -> considering resource location as relative to the current file. 246 try { 247 int importCount; 248 Resource relativeResource = getReaderContext().getResource().createRelative(location); 249 if (relativeResource.exists()) { 250 importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); 251 actualResources.add(relativeResource); 252 } 253 else { 254 String baseLocation = getReaderContext().getResource().getURL().toString(); 255 importCount = getReaderContext().getReader().loadBeanDefinitions( 256 StringUtils.applyRelativePath(baseLocation, location), actualResources); 257 } 258 if (logger.isTraceEnabled()) { 259 logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]"); 260 } 261 } 262 catch (IOException ex) { 263 getReaderContext().error("Failed to resolve current resource location", ele, ex); 264 } 265 catch (BeanDefinitionStoreException ex) { 266 getReaderContext().error( 267 "Failed to import bean definitions from relative location [" + location + "]", ele, ex); 268 } 269 } 270 Resource[] actResArray = actualResources.toArray(new Resource[0]); 271 getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); 272 } 273 274 /** 275 * Process the given alias element, registering the alias with the registry. 276 */ 277 protected void processAliasRegistration(Element ele) { 278 String name = ele.getAttribute(NAME_ATTRIBUTE); 279 String alias = ele.getAttribute(ALIAS_ATTRIBUTE); 280 boolean valid = true; 281 if (!StringUtils.hasText(name)) { 282 getReaderContext().error("Name must not be empty", ele); 283 valid = false; 284 } 285 if (!StringUtils.hasText(alias)) { 286 getReaderContext().error("Alias must not be empty", ele); 287 valid = false; 288 } 289 if (valid) { 290 try { 291 getReaderContext().getRegistry().registerAlias(name, alias); 292 } 293 catch (Exception ex) { 294 getReaderContext().error("Failed to register alias '" + alias + 295 "' for bean with name '" + name + "'", ele, ex); 296 } 297 getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); 298 } 299 } 300 301 /** 302 * Process the given bean element, parsing the bean definition 303 * and registering it with the registry. 304 */ 305 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { 306 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 307 if (bdHolder != null) { 308 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); 309 try { 310 // Register the final decorated instance. 311 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); 312 } 313 catch (BeanDefinitionStoreException ex) { 314 getReaderContext().error("Failed to register bean definition with name '" + 315 bdHolder.getBeanName() + "'", ele, ex); 316 } 317 // Send registration event. 318 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); 319 } 320 } 321 322 323 /** 324 * Allow the XML to be extensible by processing any custom element types first, 325 * before we start to process the bean definitions. This method is a natural 326 * extension point for any other custom pre-processing of the XML. 327 * <p>The default implementation is empty. Subclasses can override this method to 328 * convert custom elements into standard Spring bean definitions, for example. 329 * Implementors have access to the parser's bean definition reader and the 330 * underlying XML resource, through the corresponding accessors. 331 * @see #getReaderContext() 332 */ 333 protected void preProcessXml(Element root) { 334 } 335 336 /** 337 * Allow the XML to be extensible by processing any custom element types last, 338 * after we finished processing the bean definitions. This method is a natural 339 * extension point for any other custom post-processing of the XML. 340 * <p>The default implementation is empty. Subclasses can override this method to 341 * convert custom elements into standard Spring bean definitions, for example. 342 * Implementors have access to the parser's bean definition reader and the 343 * underlying XML resource, through the corresponding accessors. 344 * @see #getReaderContext() 345 */ 346 protected void postProcessXml(Element root) { 347 } 348 349}