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