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

com.socialbakers.config.AbstractConfiguration Maven / Gradle / Ivy

package com.socialbakers.config;

import static org.apache.commons.beanutils.PropertyUtils.getPropertyDescriptors;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.internal.core.Assert;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.socialbakers.config.exception.ConfigurationException;
import com.socialbakers.config.generator.GenerateConfig;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;

/**
 * @author Robert Fišer
 * 
 */
public abstract class AbstractConfiguration {

	private static final String NAME_PREFIX = "--";
	private static final String OPTION_PREFIX = "-";
	private static final String HELP_NAME = NAME_PREFIX + GenerateConfig.HELP;
	private static final String HELP_OPTION = OPTION_PREFIX + GenerateConfig.HELP;
	private static final String DUMP_NAME = NAME_PREFIX + GenerateConfig.DUMP;
	private static final String DUMP_OPTION = OPTION_PREFIX + GenerateConfig.DUMP;

	private final Logger logger = LoggerFactory.getLogger(getClass());

	private File confDir = new File("conf/");
	private List configFiles = new ArrayList();
	private String[] args = new String[0];
	private String helpName = "app-name";
	private String helpDescription = "";
	private List confDefs;

	private Map properties;
	private Map byName = new HashMap();
	private Map byEnv = new HashMap();
	private Map byOption = new HashMap();
	private Map byOrder = new HashMap();

	private boolean suspendValidation;

	private Comparator paramOrderComparator = new Comparator() {
		@Override
		public int compare(IParamDefinition o1, IParamDefinition o2) {
			if (o1.getOrder() == null && o2.getOrder() == null) {
				return 0;
			} else if (o1.getOrder() == null) {
				return 1;
			} else if (o2.getOrder() == null) {
				return -1;
			}
			return o1.getOrder() - o2.getOrder();
		}
	};

	protected AbstractConfiguration(IParamDefinition[] confDefs, String helpName, String helpDescription) {

		this.confDefs = new ArrayList(Arrays.asList(confDefs));
		Collections.sort(this.confDefs, paramOrderComparator);

		if (StringUtils.isNotBlank(helpName)) {
			this.helpName = helpName;
		}

		if (StringUtils.isNotBlank(helpDescription)) {
			this.helpDescription = helpDescription;
		}

		for (IParamDefinition confDef : confDefs) {
			if (StringUtils.isNotBlank(confDef.getName())) {
				byName.put(confDef.getName(), confDef);
			}
			if (StringUtils.isNotBlank(confDef.getEnv())) {
				byEnv.put(confDef.getEnv(), confDef);
			}
			if (StringUtils.isNotBlank(confDef.getOption())) {
				byOption.put(confDef.getOption(), confDef);
			}
			if (confDef.getOrder() != null) {
				byOrder.put(confDef.getOrder(), confDef);
			}
		}
	}

	public void addConfigFile(File file) {
		configFiles.add(file);
	}

	public void addConfigFile(String filename) {
		try {
			File configFile = new File(confDir, filename);
			addConfigFile(configFile);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	protected final void doValidate() {
		if (!suspendValidation) {
			validate();
		}
	}

	protected void reload() {
		try {
			reloadFromFiles();
			reloadFromEnvVars();
			reloadFromArgs();
			doValidate();
		} catch (ConfigurationException e) {
			throw e;
		} catch (Exception e) {
			throw new ConfigurationException(e);
		}
	}

	protected void setArgs(String... args) {

		this.args = args;

		for (String arg : args) {
			Assert.isNotNull(arg);
			if (arg.startsWith(HELP_NAME) || arg.startsWith(HELP_OPTION)) {
				String msg;
				try {
					msg = helpMsg();
				} catch (Exception e) {
					msg = e.getMessage();
				}
				throw new ConfigurationException(msg);
			}
			if (arg.startsWith(DUMP_NAME) || arg.startsWith(DUMP_OPTION)) {
				String msg;
				suspendValidation = true;
				reload();
				try {
					msg = dump();
				} catch (Exception e) {
					msg = e.getMessage();
				}
				throw new ConfigurationException(msg);
			}
		}
	}

	protected void validate() {
		StringBuilder msg = new StringBuilder();

		boolean valid;

		try {
			List notSetRequiredParams = new ArrayList();
			for (IParamDefinition confDef : byName.values()) {
				PropertyDescriptor descriptor = getProperties().get(confDef);
				Object value = descriptor.getReadMethod().invoke(this);
				if (value == null) {
					notSetRequiredParams.add(confDef);
				}
			}
			valid = notSetRequiredParams.isEmpty();

			msg.append("You must pass/set at least ");
			msg.append(notSetRequiredParams.size());
			msg.append(" parameters:");
			msg.append(usage(notSetRequiredParams));
			msg.append("\n");

			msg.append("All options:");
			msg.append(usage(confDefs));
			msg.append("\n");

			msg.append(dump());

		} catch (Exception e) {
			throw new ConfigurationException(e);
		}

		if (!valid) {
			throw new ConfigurationException(msg.toString());
		}
	}

	private String dump() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		StringBuilder sb = new StringBuilder();
		sb.append("ACTUAL STATE:");
		sb.append("\n");
		for (IParamDefinition confDef : confDefs) {
			PropertyDescriptor descriptor = getProperties().get(confDef);
			Object value = descriptor.getReadMethod().invoke(this);
			// sb.append(" " + formatOption(confDef, value, false));
			sb.append(confDef.getName());
			sb.append(":\t");
			sb.append(value);
			sb.append("\n");
		}
		return sb.toString();
	}

	private String[] extractValue(String arg, String prefix1, Collection prefixes2) {
		String[] result = new String[2];
		arg = arg.replaceFirst(prefix1, "");
		for (String prefix2 : prefixes2) {
			if (arg.startsWith(prefix2)) {
				result[0] = prefix2;
				result[1] = arg.replaceFirst(prefix2, "");
			}
		}
		if (StringUtils.isBlank(result[0]) || StringUtils.isBlank(result[1])) {
			throw new ConfigurationException("Invalid argument " + arg);
		}
		return result;
	}

	private String formatOption(IParamDefinition confDef, Object value, boolean bracets) {
		if (bracets) {
			value = String.format("<%s>", value.toString());
		}
		if (confDef.getOrder() != null) {
			return value.toString();
		} else if (StringUtils.isNotBlank(confDef.getOption())) {
			return OPTION_PREFIX + confDef.getOption() + value;
		} else {
			return NAME_PREFIX + confDef.getName() + value;
		}
	}

	private Map getProperties() {
		if (properties != null) {
			return properties;
		}
		properties = new HashMap();
		for (PropertyDescriptor descriptor : getPropertyDescriptors(this)) {
			String name = descriptor.getName();
			IParamDefinition confDef = byName.get(name);
			properties.put(confDef, descriptor);
		}
		return properties;
	}

	private String helpMsg() throws Exception {

		Configuration cfg = new Configuration();
		cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
		cfg.setClassForTemplateLoading(AbstractConfiguration.class, "templates");

		Template helpTemplate = cfg.getTemplate("help.ftl");

		Map input = new HashMap();
		input.put("name", helpName);
		input.put("description", helpDescription);
		input.put("usage", usage(confDefs));
		input.put("params", confDefs);
		input.put("helpName", HELP_NAME);
		input.put("helpOption", HELP_OPTION);
		input.put("dumpName", DUMP_NAME);
		input.put("dumpOption", DUMP_OPTION);
		input.put("namePrefix", NAME_PREFIX);
		input.put("optionPrefix", OPTION_PREFIX);
		input.put("configFiles", configFiles);

		StringWriter writer = new StringWriter();
		try {
			helpTemplate.process(input, writer);
			return writer.toString();
		} finally {
			writer.close();
		}
	}

	private void reloadFromArgs() {
		for (int i = 0; i < args.length; i++) {
			String arg = args[i];
			String value;
			IParamDefinition confDef;
			String source = arg;
			Set skipArgs = new HashSet(Arrays.asList(HELP_NAME, HELP_OPTION, DUMP_NAME, DUMP_OPTION));
			if (skipArgs.contains(arg)) {
				continue;
			}
			if (arg.startsWith(NAME_PREFIX)) {
				String[] e = extractValue(arg, NAME_PREFIX, byName.keySet());
				confDef = byName.get(e[0]);
				value = e[1];
			} else if (arg.startsWith(OPTION_PREFIX)) {
				String[] e = extractValue(arg, OPTION_PREFIX, byOption.keySet());
				confDef = byOption.get(e[0]);
				value = e[1];
			} else {
				confDef = byOrder.get(i);
				value = arg;
				source = "arg[" + i + "]=" + arg;
			}

			setValue(confDef, value, ConfigSource.ARG, source);
		}
	}

	private void reloadFromEnvVars() {
		for (IParamDefinition confDef : byEnv.values()) {
			if (System.getenv(confDef.getEnv()) != null) {
				String value = System.getenv(confDef.getEnv());
				setValue(confDef, value, ConfigSource.ENV, confDef.getEnv());
			}
		}
	}

	private void reloadFromFiles() {

		SAXBuilder builder = new SAXBuilder();

		for (File configFile : configFiles) {

			try {
				if (!configFile.exists()) {
					logger.info("Config file '{}' does not exists", configFile.getAbsolutePath());
					continue;
				}
				logger.info("Reading configuration from file '{}':", configFile.getAbsolutePath());
				Document document = builder.build(configFile);
				Element rootNode = document.getRootElement();
				List list = rootNode.getChildren("property");

				for (int i = 0; i < list.size(); i++) {

					Element node = (Element) list.get(i);

					String name = node.getChildText("name");
					String value = node.getChildText("value");
					IParamDefinition confDef = byName.get(name);

					setValue(confDef, value, ConfigSource.FILE, configFile.getName());
				}

			} catch (Exception e) {
				throw new ConfigurationException(e);
			}
		}
	}

	private void setValue(IParamDefinition confDef, String stringValue, ConfigSource sourceType, String source) {

		try {

			PropertyDescriptor descriptor = getProperties().get(confDef);
			Class type = descriptor.getPropertyType();

			Object value;
			if (type.isAssignableFrom(String.class)) {
				value = stringValue;
			} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
				value = Integer.valueOf(stringValue);
			} else if (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type)) {
				value = Long.valueOf(stringValue);
			} else {
				throw new ConfigurationException("Unsupported type " + type.getName());
			}

			Object previous = descriptor.getReadMethod().invoke(this);
			if (previous == null || !previous.equals(value)) {
				logger.info("Setting value '{}' of property '{}' from source {} '{}'", value, confDef.getName(),
						sourceType, source);
			}

			boolean hold = suspendValidation;
			suspendValidation = true;
			descriptor.getWriteMethod().invoke(this, value);
			suspendValidation = hold;

		} catch (Exception e) {
			throw new ConfigurationException(e);
		}
	}

	private String usage(Collection params) {
		StringBuilder sb = new StringBuilder();
		for (IParamDefinition confDef : params) {
			sb.append(" ");
			if (!confDef.isRequired()) {
				sb.append("[");
			}
			sb.append(formatOption(confDef, confDef.getName(), true));
			if (!confDef.isRequired()) {
				sb.append("]");
			}
		}
		return sb.toString();
	}

	private enum ConfigSource {
		ARG, ENV, FILE
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy