com.nearinfinity.honeycomb.config.ConfigurationParser.java Source code

Java tutorial

Introduction

Here is the source code for com.nearinfinity.honeycomb.config.ConfigurationParser.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 *
 * Copyright 2013 Near Infinity Corporation.
 */

package com.nearinfinity.honeycomb.config;

import static java.lang.String.format;

import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.google.common.io.InputSupplier;
import com.nearinfinity.honeycomb.util.Verify;

/**
 * Provides capabilities for validation and parsing of the application configuration content
 */
public class ConfigurationParser {
    private static final Logger logger = Logger.getLogger(ConfigurationParser.class);

    /**
     * XPath query to find adapter names from document.
     */
    private static final String QUERY_ADAPTER_NAMES = "/options/adapters/adapter/@name";

    /**
     * XPath query to find adapter configuration options from document
     */
    private static final String QUERY_ADAPTER_CONFIG_NODES = "/options/adapters/adapter[@name='%s']/configuration/*";

    private static final String QUERY_DEFAULT_ADAPTER = "/options/defaultAdapter";

    private static final XPath xPath = XPathFactory.newInstance().newXPath();

    private ConfigurationParser() {
    }

    /**
     * Create a {@link HoneycombConfiguration} from a configuration file path and configuration
     * schema validator path.
     *
     * @param configPath Path to configuration file to be parsed
     * @param schemaPath Path to schema file to be validated against
     * @return ConfigurationParser object holding configuration options
     */
    public static HoneycombConfiguration parseConfiguration(String configPath, String schemaPath) {
        Verify.isNotNullOrEmpty(configPath);
        Verify.isNotNullOrEmpty(schemaPath);

        final File configFile = new File(configPath);
        final File schemaFile = new File(schemaPath);

        checkFileAvailable(configFile);
        checkFileAvailable(schemaFile);

        final InputSupplier<? extends InputStream> configSupplier = Files.newInputStreamSupplier(configFile);
        final InputSupplier<? extends InputStream> schemaSupplier = Files.newInputStreamSupplier(schemaFile);

        checkValidConfig(configSupplier, schemaSupplier);
        Document doc = parseDocument(configSupplier);
        Map<String, Map<String, String>> adapters = parseAdapters(doc);
        String defaultAdapter = parseDefaultAdapter(doc);
        return new HoneycombConfiguration(adapters, defaultAdapter);
    }

    /**
     * Performs validation on the configuration content supplied by the
     * configuration supplier against the schema document provided by the
     * validation supplier.  Throws Runtime exception if validation fails.
     *
     * @param configSupplier The supplier that provides the configuration to inspect, not null
     * @param schemaSupplier The supplier that provides the schema used to inspect the configuration, not null
     */
    private static void checkValidConfig(final InputSupplier<? extends InputStream> configSupplier,
            final InputSupplier<? extends InputStream> schemaSupplier) {
        try {
            final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            final Schema schema = schemaFactory.newSchema(new StreamSource(schemaSupplier.getInput()));
            final Validator validator = schema.newValidator();
            validator.validate(new StreamSource(configSupplier.getInput()));
        } catch (Exception e) {
            logger.error("Unable to validate honeycomb configuration.", e);
            throw new RuntimeException("Exception while validating honeycomb configuration.", e);
        }
    }

    /**
     * Checks if the file is accessible and available for reading
     *
     * @param file The file to inspect
     * @return True if file is available, False otherwise
     */
    private static boolean checkFileAvailable(final File file) {
        if (!(file.exists() && file.canRead() && file.isFile())) {
            final String errorMsg = format("%s is not readable.", file.getAbsolutePath());
            logger.fatal(errorMsg);
            throw new RuntimeException(errorMsg);
        }

        return true;
    }

    /**
     * Parses the application configuration and returns the XML document.
     * @param configSupplier File supplier containing application configuration
     * @return XML Document
     */
    private static Document parseDocument(final InputSupplier<? extends InputStream> configSupplier) {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(configSupplier.getInput());

        } catch (Exception e) {
            logger.error("Unable to parse honeycomb configuration.", e);
            throw new RuntimeException("Exception while parsing honeycomb configuration.", e);
        }
    }

    /**
     * Determines if the option name is already namespaced (contains a '.').
     * @param optionName
     * @return
     */
    private static boolean isNamespaced(String optionName) {
        return optionName.contains(".");
    }

    /**
     * Namespaces the given option name by {@value Constants#HONEYCOMB_NAMESPACE} and the adapter type.
     * @param adapterName
     * @param optionName
     * @return
     */
    private static String prependNamespace(String adapterName, String optionName) {
        return Constants.HONEYCOMB_NAMESPACE + "." + adapterName + "." + optionName;
    }

    private static Map<String, String> parseOptions(String adapterName, Document doc) {
        String optionsQuery = String.format(QUERY_ADAPTER_CONFIG_NODES, adapterName);
        NodeList optionNodes;
        try {
            optionNodes = (NodeList) xPath.evaluate(optionsQuery, doc, XPathConstants.NODESET);
        } catch (XPathExpressionException e) {
            logger.error("Unable to parse options for " + adapterName + " adapter.", e);
            throw new RuntimeException("Exception while parsing options for " + adapterName + " adapter.", e);
        }
        ImmutableMap.Builder<String, String> options = ImmutableMap.builder();

        for (int i = 0; i < optionNodes.getLength(); i++) {
            Node optionNode = optionNodes.item(i);
            if (optionNode.getNodeType() == Node.ELEMENT_NODE) {
                String optionName = optionNode.getNodeName();
                String namespacedOptionName = isNamespaced(optionName) ? optionName
                        : prependNamespace(adapterName, optionName);
                options.put(namespacedOptionName, optionNode.getTextContent());
            }
        }

        return options.build();
    }

    private static Map<String, Map<String, String>> parseAdapters(Document doc) {
        // Extract adapter names from document
        NodeList adapterNameNodes;
        try {
            adapterNameNodes = (NodeList) xPath.evaluate(QUERY_ADAPTER_NAMES, doc, XPathConstants.NODESET);
        } catch (XPathExpressionException e) {
            logger.error("Unable to parse adapter names from honeycomb configuration.", e);
            throw new RuntimeException("Exception while parsing adapter names from honeycomb configuration.", e);
        }
        List<String> adapterNames = Lists.newArrayList();
        for (int i = 0; i < adapterNameNodes.getLength(); i++) {
            Node adapterNameNode = adapterNameNodes.item(i);
            if (adapterNameNode.getNodeType() == Node.ATTRIBUTE_NODE) {
                adapterNames.add(adapterNameNode.getNodeValue());
            }
        }

        // Extract individual adapter options from the document
        ImmutableMap.Builder<String, Map<String, String>> adapters = ImmutableMap.builder();
        for (String adapterName : adapterNames) {
            adapters.put(adapterName, parseOptions(adapterName, doc));
        }

        return adapters.build();
    }

    private static String parseDefaultAdapter(Document doc) {
        try {
            Node defaultAdapterNode = (Node) xPath.evaluate(QUERY_DEFAULT_ADAPTER, doc, XPathConstants.NODE);
            return defaultAdapterNode.getTextContent();
        } catch (XPathExpressionException e) {
            logger.error("Unable to parse default adapter from honeycomb configuration.", e);
            throw new RuntimeException("Exception while parsing default adapter from honeycomb configuration.", e);
        }
    }
}