001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.builder.xml;
018
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.Properties;
024
025import javax.xml.XMLConstants;
026import javax.xml.parsers.DocumentBuilder;
027import javax.xml.parsers.DocumentBuilderFactory;
028import javax.xml.parsers.ParserConfigurationException;
029
030import org.w3c.dom.Document;
031
032import org.xml.sax.ErrorHandler;
033import org.xml.sax.SAXException;
034import org.xml.sax.SAXParseException;
035
036import org.apache.camel.util.ObjectHelper;
037import org.apache.camel.util.StringHelper;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * XML XPathBuilder  support.
043 */
044public class XPathBuilderSupport {
045
046    private static final ErrorHandler DOCUMENT_BUILDER_LOGGING_ERROR_HANDLER = new DocumentBuilderLoggingErrorHandler();
047    private static final String DOCUMENT_BUILDER_FACTORY_FEATURE = "org.apache.camel.xmlconverter.documentBuilderFactory.feature";
048
049    private static final Logger LOG = LoggerFactory.getLogger(XPathBuilderSupport.class);
050
051    private volatile DocumentBuilderFactory documentBuilderFactory;
052
053
054    public DocumentBuilderFactory createDocumentBuilderFactory() {
055        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
056        factory.setNamespaceAware(true);
057        factory.setIgnoringElementContentWhitespace(true);
058        factory.setIgnoringComments(true);
059        try {
060            // Set secure processing
061            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
062        } catch (ParserConfigurationException e) {
063            LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}.", new Object[] {XMLConstants.FEATURE_SECURE_PROCESSING, true, e});
064        }
065        try {
066            // Disable the external-general-entities by default
067            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
068        } catch (ParserConfigurationException e) {
069            LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}.",
070                    new Object[] {"http://xml.org/sax/features/external-general-entities", false, e});
071        }
072        // setup the SecurityManager by default if it's apache xerces
073        try {
074            Class<?> smClass = ObjectHelper.loadClass("org.apache.xerces.util.SecurityManager");
075            if (smClass != null) {
076                Object sm = smClass.getDeclaredConstructor().newInstance();
077                // Here we just use the default setting of the SeurityManager
078                factory.setAttribute("http://apache.org/xml/properties/security-manager", sm);
079            }
080        } catch (Exception e) {
081            LOG.warn("DocumentBuilderFactory doesn't support the attribute {}, due to {}.", new Object[] {"http://apache.org/xml/properties/security-manager", e});
082        }
083        // setup the feature from the system property
084        setupFeatures(factory);
085        return factory;
086    }
087
088    protected void setupFeatures(DocumentBuilderFactory factory) {
089        Properties properties = System.getProperties();
090        List<String> features = new ArrayList<>();
091        for (Map.Entry<Object, Object> prop : properties.entrySet()) {
092            String key = (String)prop.getKey();
093            if (key.startsWith(DOCUMENT_BUILDER_FACTORY_FEATURE)) {
094                String uri = StringHelper.after(key, ":");
095                Boolean value = Boolean.valueOf((String)prop.getValue());
096                try {
097                    factory.setFeature(uri, value);
098                    features.add("feature " + uri + " value " + value);
099                } catch (ParserConfigurationException e) {
100                    LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}.", uri, value, e);
101                }
102            }
103        }
104        if (features.size() > 0) {
105            StringBuilder featureString = new StringBuilder();
106            // just log the configured feature
107            for (String feature : features) {
108                if (featureString.length() != 0) {
109                    featureString.append(", ");
110                }
111                featureString.append(feature);
112            }
113            LOG.info("DocumentBuilderFactory has been set with features {{}}.", featureString);
114        }
115
116    }
117
118    public Document createDocument() throws ParserConfigurationException {
119        DocumentBuilder builder = createDocumentBuilder();
120        return builder.newDocument();
121    }
122
123    public DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory) throws ParserConfigurationException {
124        DocumentBuilder builder = factory.newDocumentBuilder();
125        builder.setErrorHandler(DOCUMENT_BUILDER_LOGGING_ERROR_HANDLER);
126        return builder;
127    }
128
129    public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
130        return createDocumentBuilder(getDocumentBuilderFactory());
131    }
132
133    public DocumentBuilderFactory getDocumentBuilderFactory() {
134        if (documentBuilderFactory == null) {
135            documentBuilderFactory = createDocumentBuilderFactory();
136        }
137        return documentBuilderFactory;
138    }
139
140
141    private static class DocumentBuilderLoggingErrorHandler implements ErrorHandler {
142
143        @Override
144        public void warning(SAXParseException exception) throws SAXException {
145            LOG.warn(exception.getMessage(), exception);
146        }
147
148        @Override
149        public void error(SAXParseException exception) throws SAXException {
150            LOG.error(exception.getMessage(), exception);
151        }
152
153        @Override
154        public void fatalError(SAXParseException exception) throws SAXException {
155            LOG.error(exception.getMessage(), exception);
156        }
157    }
158}