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