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