org.seedstack.seed.core.internal.application.ApplicationPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.seedstack.seed.core.internal.application.ApplicationPlugin.java

Source

/**
 * Copyright (c) 2013-2015 by The SeedStack authors. All rights reserved.
 *
 * This file is part of SeedStack, An enterprise-oriented full development stack.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.seedstack.seed.core.internal.application;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import io.nuun.kernel.api.Plugin;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.context.InitContext;
import io.nuun.kernel.api.plugin.request.ClasspathScanRequest;
import io.nuun.kernel.core.AbstractPlugin;
import jodd.props.Props;
import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.MapConfiguration;
import org.apache.commons.lang.text.StrLookup;
import org.seedstack.seed.core.api.Application;
import org.seedstack.seed.core.api.SeedException;
import org.seedstack.seed.core.internal.CorePlugin;
import org.seedstack.seed.core.spi.configuration.ConfigurationLookup;
import org.seedstack.seed.core.utils.SeedReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Plugin that initialize the application identity, storage location and configuration.
 *
 * @author adrien.lauer@mpsa.com
 */
public class ApplicationPlugin extends AbstractPlugin {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationPlugin.class);
    private static final String CONFIGURATION_LOCATION = "META-INF/configuration/";
    static final String PROPS_REGEX = ".*\\.props";
    static final String PROPERTIES_REGEX = ".*\\.properties";

    private final Map<String, String> defaultConfiguration = new ConcurrentHashMap<String, String>();
    private final ClassLoader classLoader = SeedReflectionUtils
            .findMostCompleteClassLoader(ApplicationPlugin.class);
    private final Props props = buildProps();
    private final Props propsOverride = buildProps();

    private Application application;

    @Override
    public String name() {
        return "seed-core-application-plugin";
    }

    @Override
    public String pluginPackageRoot() {
        return "META-INF.configuration";
    }

    @Override
    public InitState init(InitContext initContext) {
        ApplicationDiagnosticCollector applicationDiagnosticCollector = new ApplicationDiagnosticCollector();
        ((CorePlugin) initContext.pluginsRequired().iterator().next())
                .registerDiagnosticCollector("org.seedstack.seed.core.application", applicationDiagnosticCollector);

        Set<String> allConfigurationResources = Sets.newHashSet();

        for (String propertiesResource : initContext.mapResourcesByRegex().get(PROPERTIES_REGEX)) {
            if (propertiesResource.startsWith(CONFIGURATION_LOCATION)) {
                allConfigurationResources.add(propertiesResource);
            }
        }

        for (String propsResource : initContext.mapResourcesByRegex().get(PROPS_REGEX)) {
            if (propsResource.startsWith(CONFIGURATION_LOCATION)) {
                allConfigurationResources.add(propsResource);
            }
        }

        Map<String, Class<? extends StrLookup>> configurationLookups = new HashMap<String, Class<? extends StrLookup>>();

        for (Class<?> candidate : initContext.scannedClassesByAnnotationClass().get(ConfigurationLookup.class)) {
            ConfigurationLookup configurationLookup = candidate.getAnnotation(ConfigurationLookup.class);
            if (StrLookup.class.isAssignableFrom(candidate) && configurationLookup != null
                    && !configurationLookup.value().isEmpty()) {
                configurationLookups.put(configurationLookup.value(), candidate.asSubclass(StrLookup.class));
                LOGGER.trace("Detected configuration lookup {}", configurationLookup.value());
            }
        }

        for (String configurationResource : allConfigurationResources) {
            boolean isOverrideResource = configurationResource.endsWith(".override.properties")
                    || configurationResource.endsWith(".override.props");

            try {
                Enumeration<URL> urlEnumeration = classLoader.getResources(configurationResource);
                while (urlEnumeration.hasMoreElements()) {
                    URL url = urlEnumeration.nextElement();
                    InputStream resourceAsStream = null;

                    try {
                        resourceAsStream = url.openStream();

                        if (isOverrideResource) {
                            LOGGER.debug("Adding {} to configuration override", url.toExternalForm());
                            propsOverride.load(resourceAsStream);
                        } else {
                            LOGGER.debug("Adding {} to configuration", url.toExternalForm());
                            props.load(resourceAsStream);
                        }
                    } finally {
                        if (resourceAsStream != null) {
                            try { // NOSONAR
                                resourceAsStream.close();
                            } catch (IOException e) {
                                LOGGER.warn("Unable to close configuration resource " + configurationResource, e);
                            }
                        }
                    }
                }
            } catch (IOException e) {
                throw SeedException.wrap(e, ApplicationErrorCode.UNABLE_TO_LOAD_CONFIGURATION_RESOURCE)
                        .put("resource", configurationResource);
            }
        }

        // Determine configuration profile
        String[] profiles = getStringArray(System.getProperty("org.seedstack.seed.profiles"));
        if (profiles == null || profiles.length == 0) {
            LOGGER.info("No configuration profile selected");
            applicationDiagnosticCollector.setActiveProfiles("");
        } else {
            String activeProfiles = Arrays.toString(profiles);
            LOGGER.info("Active configuration profile(s): {}", activeProfiles);
            applicationDiagnosticCollector.setActiveProfiles(activeProfiles);
        }

        // Build configuration
        Configuration configuration = buildConfiguration(props, propsOverride, configurationLookups, profiles);
        applicationDiagnosticCollector.setConfiguration(configuration);
        Configuration coreConfiguration = configuration.subset(CorePlugin.CORE_PLUGIN_PREFIX);

        String appId = coreConfiguration.getString("application-id");
        if (appId == null || appId.isEmpty()) {
            throw SeedException.createNew(ApplicationErrorCode.MISSING_APPLICATION_IDENTIFIER).put("property",
                    CorePlugin.CORE_PLUGIN_PREFIX + ".application-id");
        }

        String appName = coreConfiguration.getString("application-name");
        if (appName == null) {
            appName = appId;
        }

        String appVersion = coreConfiguration.getString("application-version");
        if (appVersion == null) {
            appVersion = "0.0.0";
        }

        LOGGER.info("Application info: '{}' / '{}' / '{}'", appId, appName, appVersion);
        applicationDiagnosticCollector.setApplicationId(appId);
        applicationDiagnosticCollector.setApplicationName(appName);
        applicationDiagnosticCollector.setApplicationVersion(appVersion);

        String seedStorage = coreConfiguration.getString("storage");
        File seedDirectory;
        if (seedStorage == null) {
            seedDirectory = new File(new File(getUserHome(), ".seed"), appId);
        } else {
            seedDirectory = new File(seedStorage);
        }

        if (!seedDirectory.exists() && !seedDirectory.mkdirs()) {
            throw SeedException.createNew(ApplicationErrorCode.UNABLE_TO_CREATE_STORAGE_DIRECTORY).put("path",
                    seedDirectory.getAbsolutePath());
        }

        if (!seedDirectory.isDirectory()) {
            throw SeedException.createNew(ApplicationErrorCode.STORAGE_PATH_IS_NOT_A_DIRECTORY).put("path",
                    seedDirectory.getAbsolutePath());
        }

        if (!seedDirectory.canWrite()) {
            throw SeedException.createNew(ApplicationErrorCode.STORAGE_DIRECTORY_IS_NOT_WRITABLE).put("path",
                    seedDirectory.getAbsolutePath());
        }

        LOGGER.debug("Application storage location is {}", seedDirectory.getAbsolutePath());
        applicationDiagnosticCollector.setStorageLocation(seedDirectory.getAbsolutePath());

        if (coreConfiguration.getBoolean("redirect-jul", true)) {
            SLF4JBridgeHandler.removeHandlersForRootLogger();
            SLF4JBridgeHandler.install();

            LOGGER.debug(
                    "Java logging to SLF4J redirection enabled, if you're using logback be sure to have a LevelChangePropagator in your configuration");
        }

        this.application = new ApplicationImpl(appName, appId, appVersion, seedDirectory, configuration);

        return InitState.INITIALIZED;
    }

    @Override
    public Collection<ClasspathScanRequest> classpathScanRequests() {
        return classpathScanRequestBuilder().resourcesRegex(PROPERTIES_REGEX).resourcesRegex(PROPS_REGEX)
                .annotationType(ConfigurationLookup.class).build();
    }

    @Override
    public Collection<Class<? extends Plugin>> requiredPlugins() {
        Collection<Class<? extends Plugin>> plugins = new ArrayList<Class<? extends Plugin>>();
        plugins.add(CorePlugin.class);
        return plugins;
    }

    @Override
    public Object nativeUnitModule() {
        return new ApplicationModule(this.application);
    }

    /**
     * Retrieve the application object.
     *
     * @return the application object.
     */
    public Application getApplication() {
        return this.application;
    }

    /**
     * Return the inner props configuration.
     *
     * @return the inner props configuration.
     */
    public Props getProps() {
        return this.props;
    }

    /**
     * Return the default configuration.
     *
     * @return the default configuration.
     */
    public Map<String, String> getDefaultConfiguration() {
        return defaultConfiguration;
    }

    private String getUserHome() {
        String userProfile = System.getenv("USERPROFILE");
        if (userProfile != null) {
            return userProfile;
        } else {
            return System.getProperty("user.home");
        }
    }

    private String[] getStringArray(String value) {
        if (value == null) {
            return null;
        } else {
            String[] split = value.split(",");
            for (int i = 0; i < split.length; i++) {
                split[i] = split[i].trim();
            }

            if (split.length == 0) {
                return null;
            } else {
                return split;
            }
        }
    }

    private Props buildProps() {
        Props newProps = new Props();

        newProps.setSkipEmptyProps(false);
        newProps.setAppendDuplicateProps(true);

        return newProps;
    }

    private StrLookup buildStrLookup(Class<? extends StrLookup> strLookupClass,
            AbstractConfiguration abstractConfiguration) {
        try {
            try {
                return strLookupClass.getConstructor(AbstractConfiguration.class)
                        .newInstance(abstractConfiguration);
            } catch (NoSuchMethodException e1) {
                try {
                    return strLookupClass.getConstructor().newInstance();
                } catch (NoSuchMethodException e2) {
                    throw SeedException
                            .wrap(e2, ApplicationErrorCode.NO_SUITABLE_CONFIGURATION_LOOKUP_CONSTRUCTOR_FOUND)
                            .put("className", strLookupClass.getCanonicalName());
                }
            }
        } catch (Exception e) {
            throw SeedException.wrap(e, ApplicationErrorCode.UNABLE_TO_INSTANTIATE_CONFIGURATION_LOOKUP)
                    .put("className", strLookupClass.getCanonicalName());
        }
    }

    private Configuration buildConfiguration(Props props, Props propsOverride,
            Map<String, Class<? extends StrLookup>> configurationLookups, String... profiles) {
        Map<String, String> finalConfiguration = new HashMap<String, String>();
        Map<String, String> configurationMap = new HashMap<String, String>();
        Map<String, String> configurationOverrideMap = new HashMap<String, String>();

        // Extract props to maps
        props.extractProps(configurationMap, profiles);
        propsOverride.extractProps(configurationOverrideMap, profiles);

        // Put defaults to final configuration
        finalConfiguration.putAll(defaultConfiguration);

        // Put nominal to final configuration
        finalConfiguration.putAll(configurationMap);

        // Apply removal behavior
        Iterator<Map.Entry<String, String>> overrideIterator = configurationOverrideMap.entrySet().iterator();
        while (overrideIterator.hasNext()) {
            String overrideKey = overrideIterator.next().getKey();
            if (overrideKey.startsWith("-")) {
                finalConfiguration.remove(overrideKey.substring(1));
                overrideIterator.remove();
            }
        }

        // Put override to final configuration
        finalConfiguration.putAll(configurationOverrideMap);

        // Convert final configuration to an immutable Apache Commons Configuration
        MapConfiguration mapConfiguration = new MapConfiguration(
                new ImmutableMap.Builder<String, Object>().putAll(finalConfiguration).build());

        // Register configuration lookups
        for (Entry<String, Class<? extends StrLookup>> configurationLookup : configurationLookups.entrySet()) {
            mapConfiguration.getInterpolator().registerLookup(configurationLookup.getKey(),
                    buildStrLookup(configurationLookup.getValue(), mapConfiguration));
        }

        return mapConfiguration;
    }
}