Package com.avast.syringe.config

Source Code of com.avast.syringe.config.ConfigurationLoader

package com.avast.syringe.config;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;

import javax.annotation.Nullable;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.avast.syringe.config.fm.PropertiesLoader;
import com.avast.syringe.config.internal.ConfigInjector;
import com.avast.syringe.config.internal.InjectableProperty;
import com.avast.syringe.config.internal.Injection;
import com.avast.syringe.config.internal.Property;
import com.avast.syringe.config.internal.Value;
import com.avast.syringe.config.internal.XmlConfig;
import com.avast.syringe.config.internal.XmlConfigParser;
import com.avast.syringe.config.mbean.ConfigDynamicBean;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
* {@link #load(File, Class) Loads } the values of a configuration file into
* an instance of the passed configuration bean class.
*/
public final class ConfigurationLoader implements InstanceManager {

    private static Logger LOGGER = LoggerFactory.getLogger(ConfigurationLoader.class);

    /**
     * Predefined property name for config file name.
     */
    public static final String CONFIG_FILE_NAME_PROP = "_configFileName_";

    private final File configDir;
    private final Map<String, Object> instanceCache = Maps.newHashMap();

    private final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

    /**
     * The FreeMarker configuration object
     */
    private final File fmPropertiesFile;
    private final Configuration fmCfg;
    private final Map fmGlobalProperties;
    private final String appPropsFileName = "application.properties";
    private boolean registerMBeans = true;

    public ConfigurationLoader(File configDir) throws IOException {
        Preconditions.checkNotNull(configDir, "configDir");
        this.configDir = configDir;
        this.fmPropertiesFile = null;

        // FreeMarker initialization
        fmCfg = new Configuration();
        fmCfg.setDirectoryForTemplateLoading(configDir);
        fmCfg.setObjectWrapper(new DefaultObjectWrapper());

        fmGlobalProperties = loadFreeMarkerGlobalProperties();
    }
   
    public ConfigurationLoader(File configDir, File fmPropertiesFile) throws IOException {
        Preconditions.checkNotNull(configDir, "configDir");
        this.configDir = configDir;
        this.fmPropertiesFile = fmPropertiesFile;

        // FreeMarker initialization
        fmCfg = new Configuration();
        fmCfg.setDirectoryForTemplateLoading(configDir);
        fmCfg.setObjectWrapper(new DefaultObjectWrapper());

        fmGlobalProperties = loadFreeMarkerGlobalProperties();
    }

    private Map loadFreeMarkerGlobalProperties() throws IOException {
        String propFileName = appPropsFileName;
        File appPropsFile = null;
        if (fmPropertiesFile != null) {
            appPropsFile = fmPropertiesFile;
        } else {
            appPropsFile = new File(configDir, propFileName);
        }

        if (!appPropsFile.exists()) {
            LOGGER.warn("{} not found", appPropsFile.getAbsolutePath());
            return null;
        }

        Properties properties = new Properties();
        properties.load(new FileReader(appPropsFile));

        return PropertiesLoader.load(properties);
    }

    public <T> T load(Class<T> configClass) throws Exception {
        Preconditions.checkNotNull(configClass, "clazz");
        return load(configClass, configClass.getSimpleName() + ".xml");
    }

    public <T> T load(final String configFileName) throws Exception {
        return load(configFileName, null, null, null);
    }

    public <T> T load(String configFileName, final Injection.ContextualPropertyResolver customResolver,
                      final PropertyValueConverter converter, final @Nullable Function<Object, Object> enhancer) throws Exception {
        return load(configFileName, null, customResolver, converter, enhancer);
    }

    public <T> T load(String configFileName, File contextDir, final Injection.ContextualPropertyResolver customResolver,
                      final PropertyValueConverter converter, final @Nullable Function<Object, Object> enhancer) throws Exception {
        Preconditions.checkNotNull(configFileName, "file");

        File configFilePath = new File(contextDir, configFileName);
        String instanceKey = stripExtension(configFilePath.getCanonicalPath());

        @SuppressWarnings("unchecked")
        T instance = (T) instanceCache.get(instanceKey);
        if (instance != null) {
            return instance;
        }

        instance = createInstance(configFileName, contextDir, customResolver, converter, enhancer, registerMBeans, null);

        instanceCache.put(instanceKey, instance);

        return instance;


    }

    private <T> T createInstance(String configFileName, File contextDir,
                                 final Injection.ContextualPropertyResolver customResolver,
                                 final PropertyValueConverter converter,
                                 final Function<Object, Object> enhancer, boolean doRegisterMBeans,
                                 Function<ConfigInjector, Void> configInjectorCallback) throws Exception {

        contextDir = contextDir == null ? configDir : contextDir;
        configFileName = completeFileName(configFileName, contextDir);

        LOGGER.info("Loading XML configuration from {}", configFileName);

        T instance;
        XmlConfigParser xmlConfigParser = new XmlConfigParser();
        InputStream input;
        final File configFile = new File(contextDir, configFileName);
        if (configFileName.endsWith(".ftl")) {
            // the input file is a FreeMarker template, so process it
            // TODO:could be used normalize from file
            // configFileName can be in form ../configuration/Template.ftl, we have to remove ../ so fm can recognize path
            String trimedConfigFileName = configFileName;
            while(trimedConfigFileName.startsWith("../")) {
                trimedConfigFileName = trimedConfigFileName.substring(3);
            }
            input = processFreeMarkerTemplate(trimedConfigFileName);
        } else {
            input = new FileInputStream(configFile);
        }

        final XmlConfig xmlConfig;
        try {
            xmlConfig = xmlConfigParser.loadConfig(input);
        } finally {
            input.close();
        }

        Class<?> cls = Thread.currentThread().getContextClassLoader().loadClass(xmlConfig.getClassName());
        ConfigInjector<?> configInjector = ConfigInjector.forClass(cls, converter);

        final String cfgFileName = configFileName;
        instance = (T) configInjector.inject(xmlConfig.getProperties(), new Injection.ContextualPropertyResolver() {
            @Override
            public Object getProperty(InjectableProperty property, Value configValue) throws Exception {
                // Allow injection of this configuration loader
                if (ConfigurationLoader.class.isAssignableFrom(property.getType())) {
                    return ConfigurationLoader.this;
                }

                if (CONFIG_FILE_NAME_PROP.equals(property.getName())) {
                    return cfgFileName;
                }

                String refConfigFile = configValue != null ? configValue.getValue() : null;
                if (refConfigFile != null) {
                    return getReferencedObject(refConfigFile, configFile.getParentFile(), customResolver, converter,
                            enhancer);
                }

                if (customResolver != null) {
                    try {
                        return customResolver.getProperty(property, configValue);
                    } catch (NoSuchFieldException e) {
                        // ok
                    }
                }

                throw new NoSuchFieldException(property.getName());

            }
        });

        T decoratedInstance = instance;
        instance = decorateInstance(decoratedInstance, xmlConfig.getDecorators(), configFile.getParentFile(), customResolver,
                converter, enhancer);

        configInjector.notifyPostConstruct(decoratedInstance);

        if (doRegisterMBeans) {
            registerMBean(configFileName, instance, xmlConfig, configInjector);
        }

        if (enhancer != null) {
            instance = (T) enhancer.apply(instance);
        }

        if (configInjectorCallback != null) {
            configInjectorCallback.apply(configInjector);
        }

        return instance;
    }

    private <T> T decorateInstance(T instance, List<XmlConfig.Decorator> decorators, File contextDir,
                                   final Injection.ContextualPropertyResolver customResolver,
                                   final PropertyValueConverter converter,
                                   final Function<Object, Object> enhancer) throws Exception {
        if (decorators.isEmpty()) {
            return instance;
        }

        XmlConfig.Decorator decoratorDesc = decorators.get(0);
        final AtomicReference<Injection> delegateInjectionRef = new AtomicReference<Injection>();

        T decorator = createInstance(decoratorDesc.getName(), contextDir, customResolver, converter, enhancer, false,
                // this callback function looks for the delegate config property in the config injector
                new Function<ConfigInjector, Void>() {
                    @Override
                    public Void apply(@Nullable ConfigInjector configInjector) {

                        List<Injection> injections = configInjector.getInjections();
                        for (Injection injection : injections) {

                            if (injection.getProperty().isDelegate()) {
                                if (delegateInjectionRef.get() != null) {
                                    String message = String.format("Decorator class %s must contain exactly one " +
                                            "'delegate=true' config property",
                                            configInjector.getConfigClass());
                                    throw new IllegalArgumentException(message);
                                } else {
                                    delegateInjectionRef.set(injection);
                                }
                            }
                        }

                        return null;
                    }
                });


        // Connect the decorator with the decorated instance
        delegateInjectionRef.get().getProperty().setValue(decorator, instance);

        return decorateInstance(decorator, decorators.subList(1, decorators.size()), contextDir, customResolver,
                converter, enhancer);
    }

    private <T> void registerMBean(String configFileName, T instance, XmlConfig xmlConfig, ConfigInjector<?> configInjector) throws MalformedObjectNameException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
        // JMX - register the configuration MBean
        ConfigDynamicBean configDynamicBean =
                new ConfigDynamicBean(instance, xmlConfig.getClassName(), "", configInjector.getInjections(),
                        this);
        ObjectName configMBeanName = new ObjectName("com.avast.syringe.instances:type=" + xmlConfig.getClassName()
                + ",name=" + configFileName);
        mBeanServer.registerMBean(configDynamicBean, configMBeanName);
    }

    private String completeFileName(String configFileName, File contextDir) throws FileNotFoundException {
        try {
            return completeFileName_(configFileName, contextDir);
        } catch (FileNotFoundException e) {
            return completeFileName_(configFileName, configDir);
        }
    }

    private String completeFileName_(String configFileName, File contextDir) throws FileNotFoundException {
        File configFile = new File(contextDir, configFileName);
        if (configFile.exists()) {
            return configFileName;
        }

        configFile = new File(contextDir, configFileName + ".xml");
        if (configFile.exists()) {
            return configFileName + ".xml";
        }

        configFile = new File(contextDir, configFileName + ".ftl");
        if (configFile.exists()) {
            return configFileName + ".ftl";
        }

        throw new FileNotFoundException("No config file " + configFileName);
    }

    private InputStream processFreeMarkerTemplate(String configFileTemplate) throws IOException, TemplateException {
        Preconditions.checkNotNull(fmGlobalProperties, "No % file found in the config directory", appPropsFileName);

        Template template = fmCfg.getTemplate(configFileTemplate);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        OutputStreamWriter out = new OutputStreamWriter(bos);
        template.process(fmGlobalProperties, out);
        out.close();

        return new ByteArrayInputStream(bos.toByteArray());
    }

    private Object getReferencedObject(String refConfigFile, File contextDir,
                                       Injection.ContextualPropertyResolver customResolver,
                                       PropertyValueConverter converter,
                                       @Nullable Function<Object, Object> enhancer) throws Exception {
//        refConfigFile = stripExtension(refConfigFile);
//
//        Object instance = instanceCache.get(refConfigFile);
//        if (instance != null) {
//            return instance;
//        }

        return load(refConfigFile, contextDir, customResolver, converter, enhancer);
    }

    public String findRefName(Object ref) {
        if (ref == null) {
            return null;
        }

        for (Map.Entry<String, Object> instCacheEntry : instanceCache.entrySet()) {
            if (ref.equals(instCacheEntry.getValue())) {
                return instCacheEntry.getKey();
            }
        }
        return null;
    }

    private String stripExtension(String refConfigFile) {
        if (refConfigFile.endsWith(".xml") || refConfigFile.endsWith(".ftl")) {
            return refConfigFile.substring(0, refConfigFile.length() - 4);
        }

        return refConfigFile;
    }

    public <T> T load(Class<T> configClass, String configFileName) throws Exception {
        Preconditions.checkNotNull(configClass, "clazz");
        Preconditions.checkNotNull(configFileName, "file");

        ConfigInjector<T> configInjector = ConfigInjector.forClass(configClass);

        File configFile = new File(configDir, configFileName);
        T configBean;
        if (!configFile.exists() && !configInjector.containsMandatory()) {
            // the default config
            configBean = configClass.newInstance();
        } else {
            URL schemaUrl = getSchemaUrl(configClass);
            Map<String, Property> props = new XmlConfigParser(schemaUrl).loadProperties(configFile);
            configBean = configInjector.inject(props, new Injection.ContextualPropertyResolver() {
                @Override
                public Object getProperty(InjectableProperty property, Value configValue) throws Exception {
                    // Allow injection of this configuration loader
                    if (ConfigurationLoader.class.isAssignableFrom(property.getType())) {
                        return ConfigurationLoader.this;
                    }

//                    if (property.isReference()) {
//                        return findReference(property);
//                    }

                    throw new NoSuchFieldException(property.getName());
                }
            });
        }

        return configBean;
    }

    /**
     * Instantiate a new configuration object using a constructor and call method {@link #load(Class)}.
     */
    @Deprecated
    public static <T> T load(File file, Class<T> configClass) throws Exception {
        Preconditions.checkNotNull(file, "file");
        Preconditions.checkNotNull(configClass, "clazz");
        return new ConfigurationLoader(file.getParentFile()).load(configClass, file.getName());
    }

    private static URL getSchemaUrl(Class<?> configClass) {
        XmlSchemaLocation schemaLoc = configClass.getAnnotation(XmlSchemaLocation.class);
        if (schemaLoc == null) {
            String message = String.format("Class %s must be annotated with @XmlSchemaLocation", configClass);
            throw new IllegalArgumentException(message);
        }

        URL schemaUrl = configClass.getResource(schemaLoc.value());
        if (schemaUrl == null) {
            String message = String.format("Schema location %s for class %s not found", schemaLoc.value(), configClass);
            throw new IllegalArgumentException(message);
        }
        return schemaUrl;
    }

    public void setRegisterMBeans(boolean register) {
        this.registerMBeans = register;
    }

    public boolean isRegisterMBeans() {
        return registerMBeans;
    }
}
TOP

Related Classes of com.avast.syringe.config.ConfigurationLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.