org.craftercms.deployer.impl.TargetResolverImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.craftercms.deployer.impl.TargetResolverImpl.java

Source

/*
 * Copyright (C) 2007-2016 Crafter Software Corporation.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.craftercms.deployer.impl;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PreDestroy;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.configuration2.CombinedConfiguration;
import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.tree.OverrideCombiner;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.craftercms.commons.spring.ApacheCommonsConfiguration2PropertySource;
import org.craftercms.deployer.api.DeploymentPipeline;
import org.craftercms.deployer.api.TargetContext;
import org.craftercms.deployer.api.TargetResolver;
import org.craftercms.deployer.api.exceptions.DeploymentException;
import org.craftercms.deployer.utils.ConfigurationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.xml.sax.InputSource;

import static org.craftercms.deployer.impl.DeploymentConstants.DEPLOYMENT_PIPELINE_CONFIG_KEY;
import static org.craftercms.deployer.impl.DeploymentConstants.TARGET_ID_CONFIG_KEY;

/**
 * Created by alfonsovasquez on 5/12/16.
 */
@Component("targetResolver")
public class TargetResolverImpl implements TargetResolver {

    private static final Logger logger = LoggerFactory.getLogger(TargetResolverImpl.class);

    public static final String APP_CONTEXT_FILENAME_SUFFIX = "-context.xml";
    public static final String YAML_FILE_EXTENSION = "yaml";
    public static final String CONFIG_PROPERTY_SOURCE_NAME = "deploymentConfig";

    protected File configFolder;
    protected Resource baseTargetYamlConfigResource;
    protected Resource baseTargetYamlConfigOverrideResource;
    protected Resource baseTargetAppContextResource;
    protected Resource baseTargetAppContextOverrideResource;
    protected final ApplicationContext mainApplicationContext;
    protected final DeploymentPipelineFactory deploymentPipelineFactory;
    protected Map<String, TargetContext> targetContextCache;

    public TargetResolverImpl(@Value("${deployer.main.config.path}") String configPath,
            @Value("${deployer.main.config.target.base.yamlLocation}") Resource baseTargetYamlConfigResource,
            @Value("${deployer.main.config.target.base.yamlOverrideLocation}") Resource baseTargetYamlConfigOverrideResource,
            @Value("${deployer.main.config.target.base.appContextLocation}") Resource baseTargetAppContextResource,
            @Value("${deployer.main.config.target.base.appContextOverrideLocation}") Resource baseTargetAppContextOverrideResource,
            @Autowired ApplicationContext mainApplicationContext,
            @Autowired DeploymentPipelineFactory deploymentPipelineFactory) throws IOException {
        this.configFolder = new File(configPath);
        this.baseTargetYamlConfigResource = baseTargetYamlConfigResource;
        this.baseTargetYamlConfigOverrideResource = baseTargetYamlConfigOverrideResource;
        this.baseTargetAppContextResource = baseTargetAppContextResource;
        this.baseTargetAppContextOverrideResource = baseTargetAppContextOverrideResource;
        this.mainApplicationContext = mainApplicationContext;
        this.deploymentPipelineFactory = deploymentPipelineFactory;

        targetContextCache = new HashMap<>();
    }

    @PreDestroy
    public void destroy() {
        logger.info("Destroying all deployment contexts...");

        targetContextCache.values().forEach(TargetContext::destroy);
    }

    @Override
    public List<TargetContext> resolveAll() throws DeploymentException {
        Collection<File> configFiles = getCustomConfigFiles();
        List<TargetContext> targetContexts = Collections.emptyList();

        if (CollectionUtils.isNotEmpty(configFiles)) {
            destroyContextsWithNoCustomConfigFile(configFiles);

            targetContexts = resolveContextsFromCustomConfigFiles(configFiles);
        }

        return targetContexts;
    }

    protected Collection<File> getCustomConfigFiles() throws DeploymentException {
        try {
            if (configFolder.exists()) {
                Collection<File> yamlFiles = FileUtils.listFiles(configFolder, new String[] { YAML_FILE_EXTENSION },
                        false);

                if (CollectionUtils.isEmpty(yamlFiles)) {
                    logger.warn("No YAML config files found under {}", configFolder.getAbsolutePath());
                }

                return yamlFiles;
            } else {
                logger.warn("Config folder {} doesn't exist", configFolder.getAbsolutePath());

                return Collections.emptyList();
            }
        } catch (Exception e) {
            throw new DeploymentException("Error while retrieving YAML config files from " + configFolder, e);
        }
    }

    protected String getTargetIdFromFilename(File file) {
        return FilenameUtils.getBaseName(file.getName());
    }

    protected File getConfigFileFromTargetId(String deploymentId) {
        String filename = deploymentId + "." + YAML_FILE_EXTENSION;

        return new File(configFolder, filename);
    }

    protected List<TargetContext> resolveContextsFromCustomConfigFiles(Collection<File> configFiles)
            throws DeploymentException {
        List<TargetContext> targetContexts = new ArrayList<>();

        // Get current contexts
        for (File file : configFiles) {
            String targetId = getTargetIdFromFilename(file);
            TargetContext targetContext = getTargetContext(targetId, file);

            targetContexts.add(targetContext);
        }

        return targetContexts;
    }

    protected void destroyContextsWithNoCustomConfigFile(Collection<File> configFiles) {
        targetContextCache.values().removeIf(context -> {
            String contextId = context.getId();
            if (!configFiles.contains(getConfigFileFromTargetId(contextId))) {
                logger.info("No YAML config file found for target '{}' under {}. Destroying target context...",
                        contextId, configFolder);

                context.destroy();

                return true;
            } else {
                return false;
            }
        });
    }

    protected TargetContext getTargetContext(String targetId, File customConfigFile) throws DeploymentException {
        MDC.put(DeploymentConstants.TARGET_ID_MDC_KEY, targetId);
        try {
            File customAppContextResource = new File(configFolder, targetId + APP_CONTEXT_FILENAME_SUFFIX);
            TargetContext targetContext = null;

            if (targetContextCache.containsKey(targetId)) {
                targetContext = targetContextCache.get(targetId);

                // Check if the YAML config file or the app context file have changed since the context was created.
                long yamlLastModified = customConfigFile.exists() ? customConfigFile.lastModified() : 0;
                long appContextLastModified = customAppContextResource.exists()
                        ? customAppContextResource.lastModified()
                        : 0;
                long contextDateCreated = targetContext.getDateCreated().toInstant().toEpochMilli();

                // Refresh if the files have been modified.
                if (yamlLastModified >= contextDateCreated || appContextLastModified >= contextDateCreated) {
                    logger.info(
                            "Configuration files haven been updated for '{}'. A new target context will be created.",
                            targetId);

                    targetContext.destroy();

                    targetContext = null;
                }
            } else {
                logger.info("No previous target context found for '{}'. A new one will be created.", targetId);
            }

            if (targetContext == null) {
                logger.info("Creating target context for '{}'", targetId);

                targetContext = createTargetContext(targetId, customConfigFile, customAppContextResource);

                targetContextCache.put(targetId, targetContext);
            }

            return targetContext;
        } catch (Exception e) {
            throw new DeploymentException("Error while resolving context for target '" + targetId + "'", e);
        } finally {
            MDC.remove(DeploymentConstants.TARGET_ID_MDC_KEY);
        }
    }

    protected TargetContext createTargetContext(String targetId, File customConfigFile, File customAppContextFile)
            throws IOException, DeploymentException {
        HierarchicalConfiguration config = loadConfiguration(customConfigFile);
        ConfigurableApplicationContext appContext = loadApplicationContext(config, customAppContextFile);

        config.setProperty(TARGET_ID_CONFIG_KEY, targetId);

        DeploymentPipeline deploymentPipeline = getDeploymentPipeline(config, appContext);

        return new TargetContextImpl(targetId, deploymentPipeline, appContext);
    }

    protected HierarchicalConfiguration loadConfiguration(File customConfigFile)
            throws IOException, DeploymentException {
        String customConfigFilename = customConfigFile.getPath();

        logger.debug("Loading custom target YAML config at {}", customConfigFilename);

        HierarchicalConfiguration customConfig = ConfigurationUtils.loadYamlConfiguration(customConfigFile);

        if (baseTargetYamlConfigResource.exists() || baseTargetYamlConfigOverrideResource.exists()) {
            CombinedConfiguration combinedConfig = new CombinedConfiguration(new OverrideCombiner());

            combinedConfig.addConfiguration(customConfig);

            if (baseTargetYamlConfigOverrideResource.exists()) {
                logger.debug("Loading base target YAML config override at {}",
                        baseTargetYamlConfigOverrideResource);

                combinedConfig.addConfiguration(
                        ConfigurationUtils.loadYamlConfiguration(baseTargetYamlConfigOverrideResource));
            }
            if (baseTargetYamlConfigResource.exists()) {
                logger.debug("Loading base target YAML config at {}", baseTargetYamlConfigResource);

                combinedConfig
                        .addConfiguration(ConfigurationUtils.loadYamlConfiguration(baseTargetYamlConfigResource));
            }

            return combinedConfig;
        } else {
            return customConfig;
        }
    }

    protected ConfigurableApplicationContext loadApplicationContext(HierarchicalConfiguration config,
            File customDeploymentAppContextFile) throws IOException {
        GenericApplicationContext appContext = new GenericApplicationContext(mainApplicationContext);

        MutablePropertySources propertySources = appContext.getEnvironment().getPropertySources();
        propertySources
                .addFirst(new ApacheCommonsConfiguration2PropertySource(CONFIG_PROPERTY_SOURCE_NAME, config));

        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
        reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);

        if (baseTargetAppContextResource.exists()) {
            logger.debug("Loading base target application context at {}", baseTargetAppContextResource);

            reader.loadBeanDefinitions(baseTargetAppContextResource);
        }
        if (baseTargetAppContextOverrideResource.exists()) {
            logger.debug("Loading base target application context override at {}", baseTargetAppContextResource);

            reader.loadBeanDefinitions(baseTargetAppContextResource);
        }
        if (customDeploymentAppContextFile.exists()) {
            logger.debug("Loading custom target application context at {}", customDeploymentAppContextFile);

            try (InputStream in = new BufferedInputStream(new FileInputStream(customDeploymentAppContextFile))) {
                reader.loadBeanDefinitions(new InputSource(in));
            }
        }

        appContext.refresh();

        return appContext;
    }

    protected DeploymentPipeline getDeploymentPipeline(HierarchicalConfiguration config,
            ApplicationContext appContext) throws DeploymentException {
        return deploymentPipelineFactory.getPipeline(config, appContext, DEPLOYMENT_PIPELINE_CONFIG_KEY, true);
    }

}