org.jsconf.core.ConfigurationFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.jsconf.core.ConfigurationFactory.java

Source

/**
 * Copyright 2013 Yves Galante
 *
 * 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.jsconf.core;

import static org.jsconf.core.impl.BeanFactory.CLASS;
import static org.jsconf.core.impl.BeanFactory.ID;
import static org.jsconf.core.impl.BeanFactory.INTERFACE;
import static org.jsconf.core.impl.BeanFactory.PROXY;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jsconf.core.impl.BeanFactory;
import org.jsconf.core.impl.ProxyPostProcessor;
import org.jsconf.core.service.ClassPathScanningCandidate;
import org.jsconf.core.service.WatchResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.StringUtils;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigSyntax;
import com.typesafe.config.ConfigValue;

public class ConfigurationFactory implements ApplicationContextAware, BeanFactoryPostProcessor, BeanPostProcessor {

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    private final Set<String> beanName = new HashSet<>();
    private final Set<String> proxyBeanName = new HashSet<>();
    private final List<WatchResource> watcher = new ArrayList<>();

    private boolean withDefinition = false;
    private boolean withProfiles = false;

    private String resourceName;

    private GenericApplicationContext context;
    private ProxyPostProcessor proxyPostProcessor;
    private ConfigParseOptions options = ConfigParseOptions.defaults().setAllowMissing(false);
    private Config config = ConfigFactory.empty();

    public void setFormat(ConfigFormat format) {
        if (format != null) {
            this.options = this.options.setSyntax(ConfigSyntax.valueOf(format.name()));
        }
    }

    public void setStrict(boolean strict) {
        this.options = this.options.setAllowMissing(!strict);
    }

    public void setDefinition(boolean withDefinition) {
        this.withDefinition = Boolean.valueOf(withDefinition);
    }

    public void setProfiles(boolean withProfiles) {
        this.withProfiles = withProfiles;
    }

    // TOOD use enum
    public ConfigurationFactory withFormat(ConfigFormat format) {
        setFormat(format);
        return this;
    }

    public ConfigurationFactory withStrict(boolean strict) {
        setStrict(strict);
        return this;
    }

    public ConfigurationFactory withDefinition(boolean def) {
        setDefinition(def);
        return this;
    }

    public ConfigurationFactory withProfiles(boolean profile) {
        setProfiles(profile);
        return this;
    }

    public void setResourceName(String resourceName) {
        this.resourceName = resourceName;
    }

    public ConfigurationFactory withResourceName(String resourceName) {
        setResourceName(resourceName);
        return this;
    }

    public void setScanPackage(String forPackage) {
        if (StringUtils.hasText(forPackage)) {
            withScanPackage(forPackage);
        }
    }

    public ConfigurationFactory withScanPackage(String forPackage) {
        ClassPathScanningCandidate candidateComponentProvider = new ClassPathScanningCandidate(false);
        candidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(ConfigurationProperties.class));
        Set<Class<?>> candidate = candidateComponentProvider.findCandidateClass(forPackage);
        for (Class<?> cl : candidate) {
            withBean(cl);
        }
        return this;
    }

    public ConfigurationFactory withBean(Class<?> bean) {
        if (bean.isAnnotationPresent(ConfigurationProperties.class)) {
            ConfigurationProperties cf = bean.getAnnotation(ConfigurationProperties.class);
            return withBean(cf.value(), bean, cf.id(), cf.proxy());
        }
        throw new BeanInitializationException(
                String.format("Missing @ConfigurationProperties annotation on class %s", bean));
    }

    public ConfigurationFactory withBean(String path, Class<?> bean) {
        return withBean(path, bean, null);
    }

    public ConfigurationFactory withBean(String path, Class<?> bean, boolean proxy) {
        return withBean(path, bean, null, proxy);
    }

    public ConfigurationFactory withBean(String path, Class<?> bean, String id) {
        return withBean(path, bean, id, false);
    }

    public ConfigurationFactory withBean(String path, Class<?> bean, String id, boolean proxy) {
        Map<String, Object> properties = new HashMap<>(2);
        if (StringUtils.hasText(id)) {
            properties.put("\"" + ID + "\"", id);
        }
        if (proxy) {
            properties.put("\"" + PROXY + "\"", true);
        }
        if (bean.isInterface()) {
            properties.put("\"" + INTERFACE + "\"", bean.getCanonicalName());
        } else {
            properties.put("\"" + CLASS + "\"", bean.getCanonicalName());
        }
        String[] splitedPath = path.split("/");
        Map<String, Map<String, ? extends Object>> object = new HashMap<>(2);
        object.put(splitedPath[splitedPath.length - 1], properties);
        for (int i = splitedPath.length - 2; i >= 0; i--) {
            Map<String, Map<String, ? extends Object>> child = object;
            object = new HashMap<>(2);
            object.put(splitedPath[i], child);
        }
        this.config = this.config.withFallback(ConfigFactory.parseMap(object));
        return this;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        loadContext();
    }

    public synchronized void reload() {
        clearContext();
        loadContext();
        this.proxyPostProcessor.forceProxyInitalization();
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (this.proxyBeanName.contains(beanName)) {
            return this.proxyPostProcessor.postProcessBeforeInitialization(bean, beanName);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = (GenericApplicationContext) applicationContext;
        this.proxyPostProcessor = new ProxyPostProcessor(applicationContext);
    }

    private void loadContext() {
        this.log.debug("Loading configuration");
        Config localConfig = this.config;
        List<String> ressources = new ArrayList<>(withProfile(this.resourceName));
        for (String resource : ressources) {
            localConfig = localConfig.withFallback(ConfigFactory.parseResourcesAnySyntax(resource, this.options));
        }
        this.log.debug("Initalize beans");
        extracted(localConfig.root().entrySet());
        this.log.debug("Beans are initalzed");
        if (!this.proxyBeanName.isEmpty()) {
            for (String resource : ressources) {
                this.watcher.add(new WatchResource(this).watch(resource));
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void extracted(Set<Entry<String, ConfigValue>> entrySet) {
        for (Entry<String, ConfigValue> entry : entrySet) {
            BeanFactory beanBuilder = new BeanFactory(this, this.context).withConfig(entry);
            if (beanBuilder.isValid()) {
                buildBeans(beanBuilder);
            } else if (entry.getValue() instanceof Map) {
                extracted(((Map<String, ConfigValue>) entry.getValue()).entrySet());
            }
        }
    }

    private void clearContext() {
        for (WatchResource watch : this.watcher) {
            watch.stop();
        }
        for (String name : this.beanName) {
            this.context.removeBeanDefinition(name);
        }
        this.watcher.clear();
        this.beanName.clear();
        this.proxyBeanName.clear();
    }

    private String buildBeans(BeanFactory beanBuilder) {
        String beanId = beanBuilder.registerBean();
        if (beanBuilder.isProxy()) {
            this.proxyBeanName.add(beanId);
        }
        this.beanName.add(beanId);
        return beanId;
    }

    private List<String> withProfile(String name) {
        List<String> ressourcesName = new ArrayList<>();
        if (this.withProfiles) {
            String[] profiles = this.context.getEnvironment().getActiveProfiles();
            for (String profile : profiles) {
                String nameWithProfile = name;
                int idx = name.lastIndexOf(".");
                if (idx > 0) {
                    nameWithProfile = nameWithProfile.subSequence(0, idx) + "-" + profile + name.substring(idx);
                } else {
                    nameWithProfile = nameWithProfile + "-" + profile;
                }
                ressourcesName.add(nameWithProfile);
            }
        }
        ressourcesName.addAll(getDefinition(name));
        return ressourcesName;
    }

    private List<String> getDefinition(String name) {
        List<String> ressourcesName = new ArrayList<>();
        if (this.withDefinition) {
            int idx = name.lastIndexOf(".");
            if (idx > 0) {
                ressourcesName.add(name.subSequence(0, idx) + ".def" + name.substring(idx));
            } else {
                ressourcesName.add(name + ".def");
            }
        }
        ressourcesName.add(name);
        return ressourcesName;
    }
}