org.atteo.moonshine.ConfigurationReader.java Source code

Java tutorial

Introduction

Here is the source code for org.atteo.moonshine.ConfigurationReader.java

Source

/*
 * Copyright 2013 Atteo.
 *
 * 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.atteo.moonshine;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.atteo.classindex.ClassFilter;
import org.atteo.classindex.ClassIndex;
import org.atteo.config.Configuration;
import org.atteo.config.IncorrectConfigurationException;
import org.atteo.config.XmlDefaultValue;
import org.atteo.config.XmlUtils;
import org.atteo.filtering.CompoundPropertyResolver;
import org.atteo.filtering.EnvironmentPropertyResolver;
import org.atteo.filtering.OneOfPropertyResolver;
import org.atteo.filtering.PropertyResolver;
import org.atteo.filtering.SystemPropertyResolver;
import org.atteo.filtering.XmlPropertyResolver;
import org.atteo.moonshine.directories.FileAccessor;
import org.atteo.moonshine.services.Service;
import org.atteo.xmlcombiner.CombineSelf;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;

public class ConfigurationReader {
    public final static String SCHEMA_FILE_NAME = "schema.xsd";
    public final static String CONFIG_FILE_NAME = "config.xml";
    public final static String AUTO_CONFIG_FILE_NAME = "auto-config.xml";
    public final static String DEFAULT_CONFIG_RESOURCE_NAME = "/default-config.xml";

    private final Configuration configuration = new Configuration();
    private final CompoundPropertyResolver customPropertyResolvers = new CompoundPropertyResolver();
    private PropertyResolver propertyResolver = null;
    private final FileAccessor fileAccessor;

    public ConfigurationReader(FileAccessor fileAccessor) {
        this.fileAccessor = fileAccessor;
    }

    public void filter() throws IncorrectConfigurationException {
        Element propertiesElement = null;
        if (configuration.getRootElement() != null) {
            NodeList nodesList = configuration.getRootElement().getElementsByTagName("properties");
            if (nodesList.getLength() == 1) {
                propertiesElement = (Element) nodesList.item(0);
            }
        }

        propertyResolver = new CompoundPropertyResolver(new OneOfPropertyResolver(), new SystemPropertyResolver(),
                new EnvironmentPropertyResolver(), new XmlPropertyResolver(propertiesElement, false),
                customPropertyResolvers, new XmlPropertyResolver(configuration.getRootElement(), true));

        configuration.filter(propertyResolver);
    }

    public Config read() throws IncorrectConfigurationException {
        return configuration.read(Config.class);
    }

    public PropertyResolver getPropertyResolver() {
        return propertyResolver;
    }

    /**
     * Generate auto-config.xml.
     */
    public void generateAutoConfiguration() throws IncorrectConfigurationException, IOException {
        Iterable<Class<? extends Service>> services = ClassFilter.only().topLevel()
                .withoutModifiers(Modifier.ABSTRACT).satisfying(TopLevelService.class::isAssignableFrom)
                .satisfying((Class<?> type) -> !containsRequiredFieldWithoutDefault(type))
                .satisfying((Class<?> type) -> {
                    ServiceConfiguration annotation = type.getAnnotation(ServiceConfiguration.class);
                    return annotation == null || annotation.auto();
                }).from(ClassIndex.getSubclasses(Service.class));

        StringBuilder builder = new StringBuilder();
        builder.append("<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
                + " xsi:noNamespaceSchemaLocation=\"" + SCHEMA_FILE_NAME + "\">\n");
        for (Class<? extends Service> service : services) {
            ServiceConfiguration annotation = service.getAnnotation(ServiceConfiguration.class);
            String name = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, service.getSimpleName());
            XmlRootElement xmlRootElement = service.getAnnotation(XmlRootElement.class);
            if (xmlRootElement != null && !"##default".equals(xmlRootElement.name())) {
                name = xmlRootElement.name();
            }
            builder.append("\t<").append(name);
            builder.append(" combine.self='").append(CombineSelf.OVERRIDABLE_BY_TAG.name());
            if (annotation == null || annotation.autoConfiguration().isEmpty()) {
                builder.append("'/>\n");
            } else {
                builder.append("'>\n");
                builder.append(annotation.autoConfiguration());
                builder.append("\n</").append(name).append(">\n");
            }
        }
        builder.append("</config>\n");

        Path autoConfigPath = fileAccessor.getWritableConfigFile(AUTO_CONFIG_FILE_NAME);
        try (Writer writer = Files.newBufferedWriter(autoConfigPath, Charsets.UTF_8)) {
            writer.write(builder.toString());
        }
    }

    /**
     * Removes auto-config.xml file.
     */
    public void removeAutoConfiguration() throws IOException {
        Path autoConfigPath = fileAccessor.getWritableConfigFile(AUTO_CONFIG_FILE_NAME);
        Files.deleteIfExists(autoConfigPath);
    }

    /**
     * Reads automatic configuration from auto-config.xml file.
     * @throws IncorrectConfigurationException when configuration is incorrect
     * @throws IOException when cannot read resource
     */
    public void combineAutoConfiguration() throws IncorrectConfigurationException, IOException {
        Path path = fileAccessor.getConfigFile(AUTO_CONFIG_FILE_NAME);
        try (InputStream stream = Files.newInputStream(path, StandardOpenOption.READ)) {
            combineConfigurationFromStream(stream);
        }
    }

    /**
     * Reads configuration from '/default-config.xml' resource.
     */
    public void combineDefaultConfiguration() {
        try {
            combineConfigurationFromResource(DEFAULT_CONFIG_RESOURCE_NAME, false);
        } catch (IOException | IncorrectConfigurationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Reads configuration from config.xml files found in ${configDirs} and ${configHome} directories.
     * @throws IncorrectConfigurationException when configuration is incorrect
     * @throws IOException when cannot read resource
     */
    public void combineConfigDirConfiguration() throws IncorrectConfigurationException, IOException {
        for (Path path : fileAccessor.getConfigFiles(CONFIG_FILE_NAME)) {
            try (InputStream stream = Files.newInputStream(path, StandardOpenOption.READ)) {
                combineConfigurationFromStream(stream);
            }
        }
    }

    /**
     * Reads configuration from given resource.
     * @param resourcePath path to the resource
     * @throws IncorrectConfigurationException when configuration is incorrect
     * @throws IOException when cannot read resource
     */
    public void combineConfigurationFromResource(String resourcePath, boolean throwIfNotFound)
            throws IncorrectConfigurationException, IOException {
        // TODO: what if more than one resource with given name?
        try (InputStream stream = getClass().getResourceAsStream(resourcePath)) {
            if (stream != null) {
                configuration.combine(stream);
            } else if (throwIfNotFound) {
                throw new RuntimeException("Configuration resource not found: " + resourcePath);
            }
        }
    }

    public void combineConfigurationFromStream(InputStream stream)
            throws IncorrectConfigurationException, IOException {
        configuration.combine(stream);
    }

    /**
     * Reads configuration from given file.
     * @param file file with configuration
     * @param throwIfNotFound whether to throw exception if file is missing
     * @throws IncorrectConfigurationException when configuration is incorrect
     * @throws IOException when cannot read file
     */
    public void combineConfigurationFromFile(File file, boolean throwIfNotFound)
            throws IncorrectConfigurationException, IOException {
        if (!file.exists()) {
            if (throwIfNotFound) {
                throw new RuntimeException("Configuration file not found: " + file.getAbsolutePath());
            } else {
                return;
            }
        }
        try (InputStream stream = new FileInputStream(file)) {
            configuration.combine(stream);
        }
    }

    /**
     * Reads configuration from given string.
     * @param string string with configuration
     * @throws IncorrectConfigurationException when configuration is incorrect
     */
    public void combineConfigurationFromString(String string) throws IncorrectConfigurationException {
        try (InputStream stream = new ByteArrayInputStream(string.getBytes(Charsets.UTF_8))) {
            configuration.combine(stream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String printCombinedXml() {
        return XmlUtils.prettyPrint(configuration.getRootElement());
    }

    public void addCustomPropertyResolver(PropertyResolver resolver) {
        customPropertyResolvers.addPropertyResolver(resolver);
    }

    public void generateTemplateConfigurationFile() throws FileNotFoundException, IOException {
        Path schemaPath = fileAccessor.getWritableConfigFile(SCHEMA_FILE_NAME);
        Files.createDirectories(schemaPath.getParent());
        configuration.generateSchema(schemaPath.toFile());

        Path configPath = fileAccessor.getWritableConfigFile(CONFIG_FILE_NAME);
        if (Files.exists(configPath)) {
            return;
        }
        try (Writer writer = Files.newBufferedWriter(configPath, Charsets.UTF_8)) {
            writer.append("<config xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
                    + " xsi:noNamespaceSchemaLocation=\"" + SCHEMA_FILE_NAME + "\">\n</config>\n");
        }
    }

    private static boolean containsRequiredFieldWithoutDefault(Class<?> type) {
        while (type != Object.class) {
            for (Field field : type.getDeclaredFields()) {
                if (field.isAnnotationPresent(XmlDefaultValue.class)) {
                    continue;
                }
                XmlElement annotation = field.getAnnotation(XmlElement.class);
                XmlAttribute annotation2 = field.getAnnotation(XmlAttribute.class);
                if (annotation != null && annotation.required()) {
                    return true;
                }
                if (annotation2 != null && annotation2.required()) {
                    return true;
                }
            }
            type = type.getSuperclass();
        }
        return false;
    }
}