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 javax.xml.parsers.DocumentBuilder;
020import javax.xml.parsers.DocumentBuilderFactory;
021import javax.xml.parsers.ParserConfigurationException;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.w3c.dom.Document;
026import org.xml.sax.EntityResolver;
027import org.xml.sax.ErrorHandler;
028import org.xml.sax.InputSource;
029
030import org.springframework.lang.Nullable;
031import org.springframework.util.xml.XmlValidationModeDetector;
032
033/**
034 * Spring's default {@link DocumentLoader} implementation.
035 *
036 * <p>Simply loads {@link Document documents} using the standard JAXP-configured
037 * XML parser. If you want to change the {@link DocumentBuilder} that is used to
038 * load documents, then one strategy is to define a corresponding Java system property
039 * when starting your JVM. For example, to use the Oracle {@link DocumentBuilder},
040 * you might start your application like as follows:
041 *
042 * <pre code="class">java -Djavax.xml.parsers.DocumentBuilderFactory=oracle.xml.jaxp.JXDocumentBuilderFactory MyMainClass</pre>
043 *
044 * @author Rob Harrop
045 * @author Juergen Hoeller
046 * @since 2.0
047 */
048public class DefaultDocumentLoader implements DocumentLoader {
049
050        /**
051         * JAXP attribute used to configure the schema language for validation.
052         */
053        private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
054
055        /**
056         * JAXP attribute value indicating the XSD schema language.
057         */
058        private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
059
060
061        private static final Log logger = LogFactory.getLog(DefaultDocumentLoader.class);
062
063
064        /**
065         * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
066         * XML parser.
067         */
068        @Override
069        public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
070                        ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
071
072                DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
073                if (logger.isTraceEnabled()) {
074                        logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
075                }
076                DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
077                return builder.parse(inputSource);
078        }
079
080        /**
081         * Create the {@link DocumentBuilderFactory} instance.
082         * @param validationMode the type of validation: {@link XmlValidationModeDetector#VALIDATION_DTD DTD}
083         * or {@link XmlValidationModeDetector#VALIDATION_XSD XSD})
084         * @param namespaceAware whether the returned factory is to provide support for XML namespaces
085         * @return the JAXP DocumentBuilderFactory
086         * @throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory
087         */
088        protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
089                        throws ParserConfigurationException {
090
091                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
092                factory.setNamespaceAware(namespaceAware);
093
094                if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
095                        factory.setValidating(true);
096                        if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
097                                // Enforce namespace aware for XSD...
098                                factory.setNamespaceAware(true);
099                                try {
100                                        factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
101                                }
102                                catch (IllegalArgumentException ex) {
103                                        ParserConfigurationException pcex = new ParserConfigurationException(
104                                                        "Unable to validate using XSD: Your JAXP provider [" + factory +
105                                                        "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
106                                                        "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
107                                        pcex.initCause(ex);
108                                        throw pcex;
109                                }
110                        }
111                }
112
113                return factory;
114        }
115
116        /**
117         * Create a JAXP DocumentBuilder that this bean definition reader
118         * will use for parsing XML documents. Can be overridden in subclasses,
119         * adding further initialization of the builder.
120         * @param factory the JAXP DocumentBuilderFactory that the DocumentBuilder
121         * should be created with
122         * @param entityResolver the SAX EntityResolver to use
123         * @param errorHandler the SAX ErrorHandler to use
124         * @return the JAXP DocumentBuilder
125         * @throws ParserConfigurationException if thrown by JAXP methods
126         */
127        protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
128                        @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
129                        throws ParserConfigurationException {
130
131                DocumentBuilder docBuilder = factory.newDocumentBuilder();
132                if (entityResolver != null) {
133                        docBuilder.setEntityResolver(entityResolver);
134                }
135                if (errorHandler != null) {
136                        docBuilder.setErrorHandler(errorHandler);
137                }
138                return docBuilder;
139        }
140
141}