All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.atteo.moonshine.ConfigurationReader Maven / Gradle / Ivy

The newest version!
/*
 * 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.config.xmlmerge.CombineSelf;
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.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> services = ClassFilter.only()
				.topLevel()
				.withoutModifiers(Modifier.ABSTRACT)
				.satisfying(new ClassFilter.Predicate() {
					@Override
					public boolean matches(Class type) {
						return TopLevelService.class.isAssignableFrom(type);
					}
				})
				.satisfying(new ClassFilter.Predicate() {
					@Override
					public boolean matches(Class type) {
						return !containsRequiredFieldWithoutDefault(type);
					}
				})
				.satisfying(new ClassFilter.Predicate() {
					@Override
					public boolean matches(Class type) {
						ServiceConfiguration annotation = type.getAnnotation(ServiceConfiguration.class);
						return annotation == null || annotation.auto();
					}
				})
				.from(ClassIndex.getSubclasses(Service.class));

		StringBuilder builder = new StringBuilder();
		builder.append("\n");
		for (Class 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().toLowerCase());
			if (annotation == null || annotation.autoConfiguration().isEmpty()) {
				builder.append("'/>\n");
			} else {
				builder.append("'>\n");
				builder.append(annotation.autoConfiguration());
				builder.append("\n\n");
			}
		}
		builder.append("\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("\n\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;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy