io.wcm.devops.conga.generator.EnvironmentGenerator.java Source code

Java tutorial

Introduction

Here is the source code for io.wcm.devops.conga.generator.EnvironmentGenerator.java

Source

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2015 wcm.io
 * %%
 * 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.
 * #L%
 */
package io.wcm.devops.conga.generator;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;

import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.google.common.collect.ImmutableMap;

import io.wcm.devops.conga.generator.handlebars.HandlebarsManager;
import io.wcm.devops.conga.generator.plugins.handlebars.escaping.NoneEscapingStrategy;
import io.wcm.devops.conga.generator.plugins.multiply.NoneMultiply;
import io.wcm.devops.conga.generator.spi.MultiplyPlugin;
import io.wcm.devops.conga.generator.spi.ValidationException;
import io.wcm.devops.conga.generator.spi.context.MultiplyContext;
import io.wcm.devops.conga.generator.spi.handlebars.EscapingStrategyPlugin;
import io.wcm.devops.conga.generator.util.EnvironmentExpander;
import io.wcm.devops.conga.generator.util.FileUtil;
import io.wcm.devops.conga.generator.util.PluginManager;
import io.wcm.devops.conga.generator.util.VariableMapResolver;
import io.wcm.devops.conga.generator.util.VariableStringResolver;
import io.wcm.devops.conga.model.environment.Environment;
import io.wcm.devops.conga.model.environment.Node;
import io.wcm.devops.conga.model.environment.NodeRole;
import io.wcm.devops.conga.model.role.Role;
import io.wcm.devops.conga.model.role.RoleFile;
import io.wcm.devops.conga.model.role.RoleVariant;
import io.wcm.devops.conga.model.util.MapMerger;

/**
 * Generates file for one environment.
 */
class EnvironmentGenerator {

    private final Map<String, Role> roles;
    private final String environmentName;
    private final Environment environment;
    private final File destDir;
    private final PluginManager pluginManager;
    private final HandlebarsManager handlebarsManager;
    private final MultiplyPlugin defaultMultiplyPlugin;
    private final String version;
    private final List<String> dependencyVersions;
    private final Logger log;

    private final Map<String, Object> environmentContextProperties;
    private final Set<String> generatedFilePaths = new HashSet<>();

    EnvironmentGenerator(Map<String, Role> roles, String environmentName, Environment environment, File destDir,
            PluginManager pluginManager, HandlebarsManager handlebarsManager, String version,
            List<String> dependencyVersions, Logger log) {
        this.roles = roles;
        this.environmentName = environmentName;
        this.environment = EnvironmentExpander.expandNodes(environment, environmentName);
        this.destDir = destDir;
        this.pluginManager = pluginManager;
        this.handlebarsManager = handlebarsManager;
        this.defaultMultiplyPlugin = pluginManager.get(NoneMultiply.NAME, MultiplyPlugin.class);
        this.version = version;
        this.dependencyVersions = dependencyVersions;
        this.log = log;
        this.environmentContextProperties = ImmutableMap.copyOf(ContextPropertiesBuilder
                .buildEnvironmentContextVariables(environmentName, this.environment, version));
    }

    public void generate() {
        log.info("");
        log.info("===== Environment '{}' =====", environmentName);

        for (Node node : environment.getNodes()) {
            generateNode(node);
        }

        log.info("");
    }

    private void generateNode(Node node) {
        if (StringUtils.isEmpty(node.getNode())) {
            throw new GeneratorException("Missing node name in " + environmentName + ".");
        }

        log.info("");
        log.info("----- Node '{}' -----", node.getNode());

        for (NodeRole nodeRole : node.getRoles()) {
            Role role = roles.get(nodeRole.getRole());
            if (role == null) {
                throw new GeneratorException("Role '" + nodeRole.getRole() + "' " + "from " + environmentName + "/"
                        + node.getNode() + " does not exist.");
            }
            String variant = nodeRole.getVariant();
            RoleVariant roleVariant = getRoleVariant(role, variant, nodeRole.getRole(), node);

            // merge default values to config
            Map<String, Object> roleConfig = roleVariant != null ? roleVariant.getConfig() : role.getConfig();
            Map<String, Object> mergedConfig = MapMerger.merge(nodeRole.getConfig(), roleConfig);

            // additionally set context variables
            mergedConfig.putAll(environmentContextProperties);
            mergedConfig.putAll(ContextPropertiesBuilder.buildCurrentContextVariables(node, nodeRole));

            // generate files
            File nodeDir = new File(destDir, node.getNode());
            if (!nodeDir.exists()) {
                nodeDir.mkdir();
            }
            for (RoleFile roleFile : role.getFiles()) {
                if (roleFile.getVariants().isEmpty() || roleFile.getVariants().contains(variant)) {
                    Template template = getHandlebarsTemplate(role, roleFile, nodeRole);
                    multiplyFiles(role, roleFile, mergedConfig, nodeDir, template, nodeRole.getRole(), variant,
                            roleFile.getTemplate());
                }
            }
        }
    }

    private RoleVariant getRoleVariant(Role role, String variant, String roleName, Node node) {
        if (StringUtils.isEmpty(variant)) {
            return null;
        }
        for (RoleVariant roleVariant : role.getVariants()) {
            if (StringUtils.equals(variant, roleVariant.getVariant())) {
                return roleVariant;
            }
        }
        throw new GeneratorException("Variant '" + variant + "' for role '" + roleName + "' " + "from "
                + environmentName + "/" + node.getNode() + " does not exist.");
    }

    private Template getHandlebarsTemplate(Role role, RoleFile roleFile, NodeRole nodeRole) {
        String templateFile = FileUtil.getTemplatePath(role, roleFile);
        if (StringUtils.isEmpty(templateFile)) {
            throw new GeneratorException(
                    "No template defined for file: " + nodeRole.getRole() + "/" + roleFile.getFile());
        }
        try {
            Handlebars handlebars = handlebarsManager.get(getEscapingStrategy(roleFile), roleFile.getCharset());
            return handlebars.compile(templateFile);
        } catch (IOException ex) {
            throw new GeneratorException(
                    "Unable to compile handlebars template: " + nodeRole.getRole() + "/" + roleFile.getFile(), ex);
        }
    }

    /**
     * Get escaping strategy for file. If one is explicitly defined in role definition use this.
     * Otherwise get the best-matching by file extension.
     * @param roleFile Role file
     * @return Escaping Strategy (never null)
     */
    private String getEscapingStrategy(RoleFile roleFile) {
        if (StringUtils.isNotEmpty(roleFile.getEscapingStrategy())) {
            return roleFile.getEscapingStrategy();
        }
        String fileExtension = FilenameUtils.getExtension(roleFile.getFile());
        return pluginManager.getAll(EscapingStrategyPlugin.class).stream()
                .filter(plugin -> !StringUtils.equals(plugin.getName(), NoneEscapingStrategy.NAME))
                .filter(plugin -> plugin.accepts(fileExtension)).findFirst()
                .orElse(pluginManager.get(NoneEscapingStrategy.NAME, EscapingStrategyPlugin.class)).getName();
    }

    private void multiplyFiles(Role role, RoleFile roleFile, Map<String, Object> config, File nodeDir,
            Template template, String roleName, String roleVariantName, String templateName) {
        MultiplyPlugin multiplyPlugin = defaultMultiplyPlugin;
        if (StringUtils.isNotEmpty(roleFile.getMultiply())) {
            multiplyPlugin = pluginManager.get(roleFile.getMultiply(), MultiplyPlugin.class);
        }

        MultiplyContext multiplyContext = new MultiplyContext().role(role).roleFile(roleFile)
                .environment(environment).config(config).logger(log);

        List<Map<String, Object>> muliplyConfigs = multiplyPlugin.multiply(multiplyContext);
        for (Map<String, Object> muliplyConfig : muliplyConfigs) {

            // resolve variables
            Map<String, Object> resolvedConfig = VariableMapResolver.resolve(muliplyConfig, false);

            // replace placeholders in dir/filename with context variables
            String dir = VariableStringResolver.resolve(roleFile.getDir(), resolvedConfig);
            String file = VariableStringResolver.resolve(roleFile.getFile(), resolvedConfig);

            generateFile(roleFile, dir, file, resolvedConfig, nodeDir, template, roleName, roleVariantName,
                    templateName);
        }
    }

    private void generateFile(RoleFile roleFile, String dir, String fileName, Map<String, Object> config,
            File nodeDir, Template template, String roleName, String roleVariantName, String templateName) {
        File file = new File(nodeDir, dir != null ? FilenameUtils.concat(dir, fileName) : fileName);
        boolean duplicateFile = generatedFilePaths.contains(FileUtil.getCanonicalPath(file));
        if (file.exists()) {
            file.delete();
        }

        // skip file if condition does not evaluate to a non-empty string or is "false"
        if (StringUtils.isNotEmpty(roleFile.getCondition())) {
            String condition = VariableStringResolver.resolve(roleFile.getCondition(), config);
            if (StringUtils.isBlank(condition) || StringUtils.equalsIgnoreCase(condition, "false")) {
                return;
            }
        }

        FileGenerator fileGenerator = new FileGenerator(environmentName, roleName, roleVariantName, templateName,
                nodeDir, file, roleFile, config, template, pluginManager, version, dependencyVersions, log);
        try {
            fileGenerator.generate();
            generatedFilePaths.add(FileUtil.getCanonicalPath(file));
            if (duplicateFile) {
                log.warn("File was generated already, check for file name clashes: "
                        + FileUtil.getCanonicalPath(file));
            }
        } catch (ValidationException ex) {
            throw new GeneratorException(
                    "File validation failed " + FileUtil.getCanonicalPath(file) + " - " + ex.getMessage());
        } catch (Throwable ex) {
            throw new GeneratorException(
                    "Unable to generate file: " + FileUtil.getCanonicalPath(file) + "\n" + ex.getMessage(), ex);
        }
    }

}