001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.beans.factory.xml;
018:
019: import java.io.IOException;
020:
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023: import org.w3c.dom.Document;
024: import org.w3c.dom.Element;
025: import org.w3c.dom.Node;
026: import org.w3c.dom.NodeList;
027:
028: import org.springframework.beans.factory.BeanDefinitionStoreException;
029: import org.springframework.beans.factory.config.BeanDefinitionHolder;
030: import org.springframework.beans.factory.parsing.BeanComponentDefinition;
031: import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
032: import org.springframework.core.io.Resource;
033: import org.springframework.core.io.support.ResourcePatternUtils;
034: import org.springframework.util.StringUtils;
035: import org.springframework.util.SystemPropertyUtils;
036: import org.springframework.util.xml.DomUtils;
037:
038: /**
039: * Default implementation of the {@link BeanDefinitionDocumentReader} interface.
040: * Reads bean definitions according to the "spring-beans" DTD and XSD format
041: * (Spring's default XML bean definition format).
042: *
043: * <p>The structure, elements and attribute names of the required XML document
044: * are hard-coded in this class. (Of course a transform could be run if necessary
045: * to produce this format). <code><beans></code> doesn't need to be the root
046: * element of the XML document: This class will parse all bean definition elements
047: * in the XML file, not regarding the actual root element.
048: *
049: * @author Rod Johnson
050: * @author Juergen Hoeller
051: * @author Rob Harrop
052: * @author Erik Wiersma
053: * @since 18.12.2003
054: */
055: public class DefaultBeanDefinitionDocumentReader implements
056: BeanDefinitionDocumentReader {
057:
058: public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
059:
060: public static final String ALIAS_ELEMENT = "alias";
061:
062: public static final String NAME_ATTRIBUTE = "name";
063:
064: public static final String ALIAS_ATTRIBUTE = "alias";
065:
066: public static final String IMPORT_ELEMENT = "import";
067:
068: public static final String RESOURCE_ATTRIBUTE = "resource";
069:
070: protected final Log logger = LogFactory.getLog(getClass());
071:
072: private XmlReaderContext readerContext;
073:
074: /**
075: * Parses bean definitions according to the "spring-beans" DTD.
076: * <p>Opens a DOM Document; then initializes the default settings
077: * specified at <code><beans></code> level; then parses
078: * the contained bean definitions.
079: */
080: public void registerBeanDefinitions(Document doc,
081: XmlReaderContext readerContext) {
082: this .readerContext = readerContext;
083:
084: logger.debug("Loading bean definitions");
085: Element root = doc.getDocumentElement();
086:
087: BeanDefinitionParserDelegate delegate = createHelper(
088: readerContext, root);
089:
090: preProcessXml(root);
091: parseBeanDefinitions(root, delegate);
092: postProcessXml(root);
093: }
094:
095: protected BeanDefinitionParserDelegate createHelper(
096: XmlReaderContext readerContext, Element root) {
097: BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(
098: readerContext);
099: delegate.initDefaults(root);
100: return delegate;
101: }
102:
103: /**
104: * Return the descriptor for the XML resource that this parser works on.
105: */
106: protected final XmlReaderContext getReaderContext() {
107: return this .readerContext;
108: }
109:
110: /**
111: * Invoke the {@link org.springframework.beans.factory.parsing.SourceExtractor} to pull the
112: * source metadata from the supplied {@link Element}.
113: */
114: protected Object extractSource(Element ele) {
115: return this .readerContext.extractSource(ele);
116: }
117:
118: /**
119: * Parse the elements at the root level in the document:
120: * "import", "alias", "bean".
121: * @param root the DOM root element of the document
122: */
123: protected void parseBeanDefinitions(Element root,
124: BeanDefinitionParserDelegate delegate) {
125: if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
126: NodeList nl = root.getChildNodes();
127: for (int i = 0; i < nl.getLength(); i++) {
128: Node node = nl.item(i);
129: if (node instanceof Element) {
130: Element ele = (Element) node;
131: String namespaceUri = ele.getNamespaceURI();
132: if (delegate.isDefaultNamespace(namespaceUri)) {
133: parseDefaultElement(ele, delegate);
134: } else {
135: delegate.parseCustomElement(ele);
136: }
137: }
138: }
139: } else {
140: delegate.parseCustomElement(root);
141: }
142: }
143:
144: private void parseDefaultElement(Element ele,
145: BeanDefinitionParserDelegate delegate) {
146: if (DomUtils.nodeNameEquals(ele, IMPORT_ELEMENT)) {
147: importBeanDefinitionResource(ele);
148: } else if (DomUtils.nodeNameEquals(ele, ALIAS_ELEMENT)) {
149: processAliasRegistration(ele);
150: } else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) {
151: processBeanDefinition(ele, delegate);
152: }
153: }
154:
155: /**
156: * Parse an "import" element and load the bean definitions
157: * from the given resource into the bean factory.
158: */
159: protected void importBeanDefinitionResource(Element ele) {
160: String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
161: if (!StringUtils.hasText(location)) {
162: getReaderContext().error(
163: "Resource location must not be empty", ele);
164: return;
165: }
166:
167: // Resolve system properties: e.g. "${user.dir}"
168: location = SystemPropertyUtils.resolvePlaceholders(location);
169:
170: if (ResourcePatternUtils.isUrl(location)) {
171: try {
172: int importCount = getReaderContext().getReader()
173: .loadBeanDefinitions(location);
174: if (logger.isDebugEnabled()) {
175: logger.debug("Imported " + importCount
176: + " bean definitions from URL location ["
177: + location + "]");
178: }
179: } catch (BeanDefinitionStoreException ex) {
180: getReaderContext().error(
181: "Failed to import bean definitions from URL location ["
182: + location + "]", ele, ex);
183: }
184: } else {
185: // No URL -> considering resource location as relative to the current file.
186: try {
187: Resource relativeResource = getReaderContext()
188: .getResource().createRelative(location);
189: int importCount = getReaderContext().getReader()
190: .loadBeanDefinitions(relativeResource);
191: if (logger.isDebugEnabled()) {
192: logger
193: .debug("Imported "
194: + importCount
195: + " bean definitions from relative location ["
196: + location + "]");
197: }
198: } catch (IOException ex) {
199: getReaderContext().error(
200: "Invalid relative resource location ["
201: + location
202: + "] to import bean definitions from",
203: ele, ex);
204: } catch (BeanDefinitionStoreException ex) {
205: getReaderContext().error(
206: "Failed to import bean definitions from relative location ["
207: + location + "]", ele, ex);
208: }
209: }
210:
211: getReaderContext().fireImportProcessed(location,
212: extractSource(ele));
213: }
214:
215: /**
216: * Process the given alias element, registering the alias with the registry.
217: */
218: protected void processAliasRegistration(Element ele) {
219: String name = ele.getAttribute(NAME_ATTRIBUTE);
220: String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
221: boolean valid = true;
222: if (!StringUtils.hasText(name)) {
223: getReaderContext().error("Name must not be empty", ele);
224: valid = false;
225: }
226: if (!StringUtils.hasText(alias)) {
227: getReaderContext().error("Alias must not be empty", ele);
228: valid = false;
229: }
230: if (valid) {
231: try {
232: getReaderContext().getRegistry().registerAlias(name,
233: alias);
234: } catch (BeanDefinitionStoreException ex) {
235: getReaderContext()
236: .error(
237: "Failed to register alias '" + alias
238: + "' for bean with name '"
239: + name + "'", ele, ex);
240: }
241: getReaderContext().fireAliasRegistered(name, alias,
242: extractSource(ele));
243: }
244: }
245:
246: /**
247: * Process the given bean element, parsing the bean definition
248: * and registering it with the registry.
249: */
250: protected void processBeanDefinition(Element ele,
251: BeanDefinitionParserDelegate delegate) {
252: BeanDefinitionHolder bdHolder = delegate
253: .parseBeanDefinitionElement(ele);
254: if (bdHolder != null) {
255: bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,
256: bdHolder);
257: try {
258: // Register the final decorated instance.
259: BeanDefinitionReaderUtils.registerBeanDefinition(
260: bdHolder, getReaderContext().getRegistry());
261: } catch (BeanDefinitionStoreException ex) {
262: getReaderContext()
263: .error(
264: "Failed to register bean definition with name '"
265: + bdHolder.getBeanName() + "'",
266: ele, ex);
267: }
268: // Send registration event.
269: getReaderContext().fireComponentRegistered(
270: new BeanComponentDefinition(bdHolder));
271: }
272: }
273:
274: /**
275: * Allow the XML to be extensible by processing any custom element types first,
276: * before we start to process the bean definitions. This method is a natural
277: * extension point for any other custom pre-processing of the XML.
278: * <p>The default implementation is empty. Subclasses can override this method to
279: * convert custom elements into standard Spring bean definitions, for example.
280: * Implementors have access to the parser's bean definition reader and the
281: * underlying XML resource, through the corresponding accessors.
282: * @see #getReaderContext()
283: */
284: protected void preProcessXml(Element root) {
285: }
286:
287: /**
288: * Allow the XML to be extensible by processing any custom element types last,
289: * after we finished processing the bean definitions. This method is a natural
290: * extension point for any other custom post-processing of the XML.
291: * <p>The default implementation is empty. Subclasses can override this method to
292: * convert custom elements into standard Spring bean definitions, for example.
293: * Implementors have access to the parser's bean definition reader and the
294: * underlying XML resource, through the corresponding accessors.
295: * @see #getReaderContext()
296: */
297: protected void postProcessXml(Element root) {
298: }
299:
300: }
|