org.wso2.carbon.datasource.utils.DataSourceUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.datasource.utils.DataSourceUtils.java

Source

/*
 *  Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.wso2.carbon.datasource.utils;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import org.wso2.carbon.datasource.core.exception.DataSourceException;
import org.wso2.carbon.kernel.utils.Utils;
import org.xml.sax.SAXException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * Data Sources utility class.
 */
public class DataSourceUtils {

    private static Logger logger = LoggerFactory.getLogger(DataSourceUtils.class);

    private static final String[] CLASS_RETURN_TYPES = { "String", "Byte", "Character", "Short", "Integer", "Float",
            "Double", "Character", "Boolean" };

    private static final String XML_DECLARATION = "xml-declaration";

    private static final String DATASOURCES_DIRECTORY_NAME = "datasources";

    private static ThreadLocal<String> dataSourceId = new ThreadLocal<String>() {
        protected synchronized String initialValue() {
            return null;
        }
    };

    public static String getCurrentDataSourceId() {
        return dataSourceId.get();
    }

    public static boolean nullAllowEquals(Object lhs, Object rhs) {
        return lhs == null && rhs == null || !((lhs == null && rhs != null) || (lhs != null && rhs == null))
                && (lhs != null && lhs.equals(rhs));
    }

    public static String elementToString(Element element) {
        try {
            if (element == null) {
                /* return an empty string because, the other way around works the same,
                where if we give a empty string as the XML, we get a null element
                from "stringToElement" */
                return "";
            }
            Document document = element.getOwnerDocument();
            DOMImplementationLS domImplLS = (DOMImplementationLS) document.getImplementation();
            LSSerializer serializer = domImplLS.createLSSerializer();
            //by default its true, so set it to false to get String without xml-declaration
            serializer.getDomConfig().setParameter(XML_DECLARATION, false);
            return serializer.writeToString(element);
        } catch (Exception e) {
            logger.error("Error while converting element to string: " + e.getMessage(), e);
            return null;
        }
    }

    public static Document convertToDocument(File file) throws DataSourceException {
        try {
            return getSecuredDocumentBuilder(false).parse(file);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            throw new DataSourceException("Error in creating an XML document from file: " + e.getMessage(), e);
        }
    }

    /**
     * This method provides a secured document builder which will secure XXE attacks.
     *
     * @param setIgnoreComments whether to set setIgnoringComments in DocumentBuilderFactory.
     * @return DocumentBuilder
     * @throws ParserConfigurationException
     */
    private static DocumentBuilder getSecuredDocumentBuilder(boolean setIgnoreComments)
            throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setIgnoringComments(setIgnoreComments);
        documentBuilderFactory.setNamespaceAware(true);
        documentBuilderFactory.setExpandEntityReferences(false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        documentBuilder.setEntityResolver((publicId, systemId) -> {
            throw new SAXException("Possible XML External Entity (XXE) attack. Skip resolving entity");
        });
        return documentBuilder;
    }

    /**
     * Returns the conf directory path located in carbon.home.
     *
     * @return {@link Path}
     */
    public static Path getDataSourceConfigPath() {
        return Utils.getCarbonConfigHome().resolve(DATASOURCES_DIRECTORY_NAME);
    }

    /**
     * Replaces system variables in the input xml configuration.
     *
     * @param xmlConfiguration InputStream that carries xml configuration
     * @return returns a InputStream that has evaluated system variables in input
     * @throws DataSourceException
     */
    public static InputStream replaceSystemVariablesInXml(InputStream xmlConfiguration) throws DataSourceException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder;
        Document doc;
        try {
            documentBuilderFactory.setNamespaceAware(true);
            documentBuilderFactory.setExpandEntityReferences(false);
            documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            //            SecurityManager securityManager = new SecurityManager();
            //            securityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT);
            //            documentBuilderFactory.setAttribute(SECURITY_MANAGER_PROPERTY, securityManager);
            documentBuilder = documentBuilderFactory.newDocumentBuilder();
            documentBuilder.setEntityResolver((publicId, systemId) -> {
                throw new SAXException("Possible XML External Entity (XXE) attack. Skip resolving entity");
            });
            doc = documentBuilder.parse(xmlConfiguration);
        } catch (ParserConfigurationException | IOException | SAXException e) {
            throw new DataSourceException("Error in building Document", e);
        }
        NodeList nodeList = null;
        if (doc != null) {
            nodeList = doc.getElementsByTagName("*");
        }
        if (nodeList != null) {
            for (int i = 0; i < nodeList.getLength(); i++) {
                resolveLeafNodeValue(nodeList.item(i));
            }
        }
        return toInputStream(doc);
    }

    /**
     * @param doc the DOM.Document to be converted to InputStream.
     * @return Returns InputStream.
     * @throws DataSourceException
     */
    public static InputStream toInputStream(Document doc) throws DataSourceException {
        InputStream in;
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            Source xmlSource = new DOMSource(doc);
            Result result = new StreamResult(outputStream);
            TransformerFactory.newInstance().newTransformer().transform(xmlSource, result);
            in = new ByteArrayInputStream(outputStream.toByteArray());
        } catch (TransformerException e) {
            throw new DataSourceException("Error in transforming DOM to InputStream", e);
        }
        return in;
    }

    public static void resolveLeafNodeValue(Node node) {
        if (node != null) {
            Element element = (Element) node;
            NodeList childNodeList = element.getChildNodes();
            for (int j = 0; j < childNodeList.getLength(); j++) {
                Node chileNode = childNodeList.item(j);
                if (!chileNode.hasChildNodes()) {
                    String nodeValue = resolveSystemProperty(chileNode.getTextContent());
                    childNodeList.item(j).setTextContent(nodeValue);
                } else {
                    resolveLeafNodeValue(chileNode);
                }
            }
        }
    }

    /**
     * Replaces system variables in the input xml configuration.
     *
     * @param xmlConfiguration String
     * @return String
     * @throws DataSourceException
     */
    public static String replaceSystemVariablesInXml(String xmlConfiguration) throws DataSourceException {
        InputStream in = replaceSystemVariablesInXml(
                new ByteArrayInputStream(xmlConfiguration.getBytes(StandardCharsets.UTF_8)));
        try {
            xmlConfiguration = IOUtils.toString(in);
        } catch (IOException e) {
            throw new DataSourceException("Error in converting InputStream to String", e);
        }
        return xmlConfiguration;
    }

    public static String resolveSystemProperty(String text) {
        int indexOfStartingChars = -1;
        int indexOfClosingBrace;

        // The following condition deals with properties.
        // Properties are specified as ${system.property},
        // and are assumed to be System properties
        while (indexOfStartingChars < text.indexOf("${") && (indexOfStartingChars = text.indexOf("${")) != -1
                && (indexOfClosingBrace = text.indexOf('}')) != -1) { // Is a
            // property
            // used?
            String sysProp = text.substring(indexOfStartingChars + 2, indexOfClosingBrace);
            String propValue = System.getProperty(sysProp);
            if (propValue != null) {
                text = text.substring(0, indexOfStartingChars) + propValue
                        + text.substring(indexOfClosingBrace + 1);
            }
            if (sysProp.equals("carbon.home") && propValue != null && propValue.equals(".")) {

                text = new File(".").getAbsolutePath() + File.separator + text;

            }
        }
        return text;
    }

    public static Map<String, String> extractPrimitiveFieldNameValuePairs(Object object)
            throws DataSourceException {
        Map<String, String> nameValueMap = new HashMap<>();
        Method methods[] = object.getClass().getMethods();
        for (Method method : methods) {
            if (isMethodMatched(method)) {
                String fieldName = getFieldNameFromMethodName(method.getName());
                try {
                    if (method.invoke(object) != null) {
                        String result = method.invoke(object).toString();
                        nameValueMap.put(fieldName, result);
                    }
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new DataSourceException("Error in retrieving " + fieldName + " value from the object :"
                            + object.getClass() + e.getMessage(), e);
                }
            }
        }
        return nameValueMap;
    }

    private static String getFieldNameFromMethodName(String name) throws DataSourceException {
        String prefixGet = "get";
        String prefixIs = "is";
        String firstLetter;

        if (name.startsWith(prefixGet)) {
            firstLetter = name.substring(3, 4);
            name = name.substring(4);
        } else if (name.startsWith(prefixIs)) {
            firstLetter = name.substring(2, 3);
            name = name.substring(3);
        } else {
            throw new DataSourceException("Error in retrieving attribute name from method : " + name);
        }
        firstLetter = firstLetter.toLowerCase(Locale.getDefault());
        return firstLetter.concat(name);
    }

    private static boolean isMethodMatched(Method method) {
        String returnType = method.getReturnType().getSimpleName();
        String methodName = method.getName();

        if (!Modifier.isPublic(method.getModifiers())) {
            return false;
        }
        if (returnType.equals("void")) {
            return false;
        }
        if (!(methodName.startsWith("get") || (methodName.startsWith("is")
                && (returnType.equals("Boolean") || returnType.equals("boolean"))))) {
            return false;
        }
        if (!(method.getReturnType().isPrimitive() || Arrays.asList(CLASS_RETURN_TYPES).contains(returnType))) {
            return false;
        }
        return true;
    }

    /**
     * Generate the configuration bean by reading the xml file or string xml content.
     *
     * @param configuration This should be either a {@link File} or a {@code String}
     * @param clazz         class type of the generated bean
     * @param <T>           class type of the generated bean
     * @param <U>           {@link File} or a {@code String}
     * @return T class type of the generated bean
     * @throws DataSourceException
     */
    public static <T, U> T loadJAXBConfiguration(U configuration, Class<T> clazz) throws DataSourceException {
        try {
            JAXBContext ctx = JAXBContext.newInstance(clazz);
            if (configuration instanceof File) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Parsing configuration file: " + ((File) configuration).getName());
                }
                Document doc = DataSourceUtils.convertToDocument((File) configuration);
                return (T) ctx.createUnmarshaller().unmarshal(doc);
            } else if (configuration instanceof String) {
                String xmlConfiguration = DataSourceUtils.replaceSystemVariablesInXml((String) configuration);
                return (T) ctx.createUnmarshaller()
                        .unmarshal(new ByteArrayInputStream(xmlConfiguration.getBytes(StandardCharsets.UTF_8)));
            } else {
                throw new DataSourceException("Only a file or string content allowed as the first parameter.");
            }
        } catch (JAXBException e) {
            throw new DataSourceException("Error occurred while converting configuration into jaxb beans", e);
        }
    }
}