com.opengamma.maven.ScriptableScriptGeneratorMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.maven.ScriptableScriptGeneratorMojo.java

Source

/**
 * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
 * 
 * Please see distribution for license.
 */
package com.opengamma.maven;

import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment;
import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId;
import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
import static org.twdata.maven.mojoexecutor.MojoExecutor.version;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;

import com.google.common.collect.Maps;
import com.opengamma.scripts.ScriptGenerator;
import com.opengamma.scripts.ScriptUtils;
import com.opengamma.scripts.Scriptable;

import freemarker.template.Template;

// CSOFF for Javadoc tags
/**
 * Generates scripts from a template based on annotated classes.
 * 
 * @goal generate-scripts
 * @phase prepare-package
 * @requiresDependencyResolution compile
 * @threadSafe
 * @description
 */
// CSON
public class ScriptableScriptGeneratorMojo extends AbstractMojo {
    // no underscore makes m2e auto-complete correct

    /**
     * Set to true to skip all processing, default false.
     * @parameter alias="skip" property="opengamma.generate.scripts.skip"
     */
    private boolean skip; // CSIGNORE
    /**
     * Where the scripts should be generated.
     * @parameter alias="outputDir" default-value="${project.build.directory}/scripts"
     * @required
     */
    private File outputDir; // CSIGNORE
    /**
     * The type to generate.
     * This is a shortcut, allowing all the files stored in the scripts project
     * to be accessed without needing to specify lots of config everywhere.
     * The only recognized value at present is 'tool'.
     * If this is set, then the unixTemplate and windowsTemplate fields will be
     * set, and a standard set of additional scripts added.
     * Use the 'unix' and 'windows' boolean flags to control which is output.
     * @parameter alias="type" property="opengamma.generate.scripts.type"
     */
    private String type; // CSIGNORE
    /**
     * True to generate unix scripts, default true.
     * @parameter alias="unix" property="opengamma.generate.scripts.unix" default-value="true"
     */
    private boolean unix; // CSIGNORE
    /**
     * The basic template file name on Unix.
     * This is used as the default template file name.
     * It effectively maps to 'java.lang.Object' in the unixTemplatesMap.
     * @parameter alias="unixTemplate"
     */
    private String unixTemplate; // CSIGNORE
    /**
     * A map of class name to template file name on Unix.
     * @parameter alias="baseClassTemplateMap"
     */
    private Map<String, String> unixTemplatesMap; // CSIGNORE
    /**
     * True to generate windows scripts, default true.
     * @parameter alias="windows" property="opengamma.generate.scripts.windows" default-value="true"
     */
    private boolean windows; // CSIGNORE
    /**
     * The basic template file name on Windows.
     * This is used as the default template file name.
     * It effectively maps to 'java.lang.Object' in the windowsTemplatesMap.
     * @parameter alias="windowsTemplate"
     */
    private String windowsTemplate; // CSIGNORE
    /**
     * A map of class name to template file name on Windows.
     * @parameter alias="baseClassTemplateMap"
     */
    private Map<String, String> windowsTemplatesMap; // CSIGNORE
    /**
     * Additional scripts to copy unchanged.
     * @parameter alias="additionalScripts"
     */
    private String[] additionalScripts; // CSIGNORE
    /**
     * Set to true to create an attached zip archive, default true.
     * @parameter alias="zip" property="opengamma.generate.scripts.zip" default-value="false"
     */
    private boolean zip; // CSIGNORE

    /**
     * The project base directory.
     * @parameter default-value="${project.basedir}"
     * @required
     * @readonly
     */
    private File baseDir; // CSIGNORE
    /**
     * @parameter default-value="${project}"
     * @required
     * @readonly
     */
    private MavenProject project; // CSIGNORE
    /**
     * The current Maven session.
     *
     * @parameter default-value="${session}"
     * @required
     * @readonly
     */
    private MavenSession mavenSession; // CSIGNORE
    /**
     * The Maven BuildPluginManager component.
     *
     * @component
     * @required
     */
    private BuildPluginManager mavenPluginManager; // CSIGNORE

    //-------------------------------------------------------------------------
    /**
     * Maven plugin for generating scripts to run tools annotated with {@link Scriptable}.
     * <p>
     * Variables available in the Freemarker template are:
     * <ul>
     * <li> className - the fully-qualified Java class name
     * </ul>
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (skip) {
            return;
        }

        if (type != null) {
            processType();
        }

        // make the output directory
        try {
            FileUtils.forceMkdir(outputDir);
        } catch (Exception ex) {
            throw new MojoExecutionException("Error creating output directory " + outputDir.getAbsolutePath(), ex);
        }

        // resolve the input
        Map<String, String> unixMap = buildInputMap(unixTemplate, unixTemplatesMap);
        Map<String, String> windowsMap = buildInputMap(windowsTemplate, windowsTemplatesMap);
        URL[] classpathUrls = buildProjectClasspath(project);
        ClassLoader classLoader = new URLClassLoader(classpathUrls, this.getClass().getClassLoader());

        // build the scripts
        if (unixMap.isEmpty() == false || windowsMap.isEmpty() == false) {
            Set<Class<?>> scriptableClasses = ScriptUtils.findClassAnnotation(classpathUrls, Scriptable.class,
                    classLoader);
            getLog().info("Generating " + scriptableClasses.size() + " scripts");
            if (unix) {
                generateScripts(unixMap, scriptableClasses, classLoader, false);
            }
            if (windows) {
                generateScripts(windowsMap, scriptableClasses, classLoader, true);
            }
        } else {
            getLog().info("No scripts to generate");
        }
        copyAdditionalScripts(classLoader);
        if (zip) {
            buildZip();
        }
    }

    //-------------------------------------------------------------------------
    // processes the type, avoiding leaking config everywhere
    private void processType() throws MojoExecutionException {
        if (type.equals("tool") == false) {
            throw new MojoExecutionException("Invalid type, only 'tool' is valid: " + type);
        }
        if (StringUtils.isNotEmpty(unixTemplate) || StringUtils.isNotEmpty(windowsTemplate)) {
            throw new MojoExecutionException("When type is set, unixTemplate and windowsTemplate must not be set");
        }
        Set<String> additional = new HashSet<>();
        if (additionalScripts != null) {
            additional.addAll(Arrays.asList(additionalScripts));
        }
        if (unix) {
            unixTemplate = "com/opengamma/scripts/templates/tool-template-unix.ftl";
            additional.add("com/opengamma/scripts/run-tool.sh");
            additional.add("com/opengamma/scripts/java-utils.sh");
            additional.add("com/opengamma/scripts/componentserver-init-utils.sh");
            additional.add("com/opengamma/scripts/templates/project-utils.sh.ftl");
        }
        if (windows) {
            windowsTemplate = "com/opengamma/scripts/templates/tool-template-windows.ftl";
            additional.add("com/opengamma/scripts/run-tool.bat");
            additional.add("com/opengamma/scripts/run-tool-noexe.bat");
            additional.add("com/opengamma/scripts/templates/project-utils.bat.ftl");
        }
        additionalScripts = (String[]) additional.toArray(new String[additional.size()]);
    }

    //-------------------------------------------------------------------------
    // merges the input template and templateMap
    private static Map<String, String> buildInputMap(String template, Map<String, String> tempateMap)
            throws MojoExecutionException {
        Map<String, String> result = Maps.newHashMap();
        if (tempateMap != null) {
            result.putAll(tempateMap);
        }
        if (template != null) {
            if (result.containsKey("java.lang.Object")) {
                throw new MojoExecutionException(
                        "Cannot specify template if templateMap contains key 'java.lang.Object'");
            }
            result.put("java.lang.Object", template);
        }
        return result;
    }

    //-------------------------------------------------------------------------
    // extracts the project classpath from Maven
    @SuppressWarnings("unchecked")
    private static URL[] buildProjectClasspath(MavenProject project) throws MojoExecutionException {
        List<String> classpathElementList;
        try {
            classpathElementList = project.getCompileClasspathElements();
        } catch (DependencyResolutionRequiredException ex) {
            throw new MojoExecutionException("Error obtaining dependencies", ex);
        }
        return ScriptUtils.getClasspathURLs(classpathElementList);
    }

    //-------------------------------------------------------------------------
    // generates the scripts
    private void generateScripts(Map<String, String> templates, Set<Class<?>> scriptableClasses,
            ClassLoader classLoader, boolean windows) throws MojoExecutionException {
        if (templates.isEmpty()) {
            return;
        }
        Map<Class<?>, Template> templateMap = resolveTemplateMap(templates, classLoader);
        for (Class<?> scriptableClass : scriptableClasses) {
            Map<String, Object> templateData = new HashMap<String, Object>();
            templateData.put(ScriptGenerator.TEMPLATE_CLASS_NAME, scriptableClass.getName());
            templateData.put(ScriptGenerator.TEMPLATE_PROJECT_NAME, project.getArtifactId());
            templateData.put(ScriptGenerator.TEMPLATE_PROJECT_JAR, project.getBuild().getFinalName() + ".jar");
            Template template = lookupTempate(scriptableClass, templateMap);
            ScriptGenerator.generate(scriptableClass.getName(), outputDir, template, templateData, windows);
        }
    }

    //-------------------------------------------------------------------------
    // resolves the template names to templates
    private Map<Class<?>, Template> resolveTemplateMap(Map<String, String> templates, ClassLoader classLoader)
            throws MojoExecutionException {
        Map<Class<?>, Template> templateMap = Maps.newHashMap();
        for (Map.Entry<String, String> unresolvedEntry : templates.entrySet()) {
            String className = unresolvedEntry.getKey();
            Class<?> clazz = resolveClass(className, classLoader);
            Template template = getTemplate(unresolvedEntry.getValue(), classLoader);
            templateMap.put(clazz, template);
        }
        return templateMap;
    }

    //-------------------------------------------------------------------------
    // load a template
    private Template getTemplate(String templateName, ClassLoader classLoader) throws MojoExecutionException {
        try {
            return ScriptGenerator.loadTemplate(baseDir, templateName, classLoader);
        } catch (Exception ex) {
            throw new MojoExecutionException("Error loading Freemarker template: " + templateName, ex);
        }
    }

    //-------------------------------------------------------------------------
    // lookup and find the best matching template
    private static Template lookupTempate(Class<?> scriptableClass, Map<Class<?>, Template> templateMap)
            throws MojoExecutionException {
        Class<?> clazz = scriptableClass;
        while (clazz != null) {
            Template template = templateMap.get(clazz);
            if (template != null) {
                return template;
            }
            clazz = clazz.getSuperclass();
        }
        throw new MojoExecutionException("No template found: " + scriptableClass);
    }

    //-------------------------------------------------------------------------
    // copies any additional scripts
    private void copyAdditionalScripts(ClassLoader classLoader) throws MojoExecutionException {
        if (additionalScripts == null || additionalScripts.length == 0) {
            getLog().info("No additional scripts to copy");
            return;
        }
        getLog().info("Copying " + additionalScripts.length + " additional script(s)");
        for (String script : additionalScripts) {
            File scriptFile = new File(baseDir, script);
            // process ftl if necessary
            if (script.endsWith(".ftl")) {
                generateAdditionalFile(classLoader, script, scriptFile);
            } else {
                // simple copy, either file or resource
                if (scriptFile.exists()) {
                    copyAdditionalFile(classLoader, script, scriptFile);
                } else {
                    copyAdditionalResource(classLoader, script, scriptFile);
                }
            }
        }
    }

    private void generateAdditionalFile(ClassLoader classLoader, String script, File scriptFile)
            throws MojoExecutionException {
        File destinationFile = new File(outputDir,
                scriptFile.getName().substring(0, scriptFile.getName().length() - 4));
        Map<String, Object> templateData = new HashMap<String, Object>();
        templateData.put(ScriptGenerator.TEMPLATE_PROJECT_NAME, project.getArtifactId());
        templateData.put(ScriptGenerator.TEMPLATE_PROJECT_JAR, project.getBuild().getFinalName() + ".jar");
        Template template = getTemplate(script, classLoader);
        ScriptGenerator.generate(destinationFile, template, templateData);
        destinationFile.setReadable(true, false);
        destinationFile.setExecutable(true, false);
    }

    private void copyAdditionalFile(ClassLoader classLoader, String script, File scriptFile)
            throws MojoExecutionException {
        if (scriptFile.isFile() == false) {
            throw new MojoExecutionException(
                    "Additional script is not a file, directories cannot be copied: " + scriptFile);
        }
        try {
            File destinationFile = new File(outputDir, scriptFile.getName());
            String content = FileUtils.readFileToString(scriptFile, StandardCharsets.UTF_8);
            content = ScriptGenerator.fixLineEndings(content, destinationFile.getName());
            FileUtils.write(destinationFile, content, StandardCharsets.UTF_8);
            destinationFile.setReadable(true, false);
            destinationFile.setExecutable(true, false);
        } catch (IOException ex) {
            throw new MojoExecutionException("Unable to copy additional script file: " + script, ex);
        }
    }

    private void copyAdditionalResource(ClassLoader classLoader, String script, File scriptFile)
            throws MojoExecutionException {
        try (InputStream resourceStream = classLoader.getResourceAsStream(script)) {
            if (resourceStream == null) {
                throw new MojoExecutionException("Additional script cannot be found: " + script);
            }
            File destinationFile = new File(outputDir, scriptFile.getName());
            FileUtils.writeByteArrayToFile(destinationFile, IOUtils.toByteArray(resourceStream));
            String content = FileUtils.readFileToString(destinationFile, StandardCharsets.UTF_8);
            content = ScriptGenerator.fixLineEndings(content, destinationFile.getName());
            FileUtils.write(destinationFile, content, StandardCharsets.UTF_8);
            destinationFile.setReadable(true, false);
            destinationFile.setExecutable(true, false);
        } catch (IOException ex) {
            throw new MojoExecutionException("Unable to copy additional script resource: " + script, ex);
        }
    }

    //-------------------------------------------------------------------------
    // loads a class
    private static Class<?> resolveClass(String className, ClassLoader classLoader) throws MojoExecutionException {
        try {
            return classLoader.loadClass(className);
        } catch (ClassNotFoundException e) {
            throw new MojoExecutionException("Unable to resolve class " + className);
        }
    }

    //-------------------------------------------------------------------------
    // process the zipping
    private void buildZip() throws MojoExecutionException {
        // pick correct assembly xml
        String descriptorResource = "assembly-scripts.xml";
        if (unix && !windows) {
            descriptorResource = "assembly-unix.xml";
        } else if (windows && !unix) {
            descriptorResource = "assembly-windows.xml";
        }

        // copy it to a real file location
        File descriptorFile = new File(outputDir, new File(descriptorResource).getName());
        try (InputStream resourceStream = getClass().getResourceAsStream(descriptorResource)) {
            if (resourceStream == null) {
                throw new MojoExecutionException("Assembly descriptor cannot be found: " + descriptorResource);
            }
            FileUtils.writeByteArrayToFile(descriptorFile, IOUtils.toByteArray(resourceStream));
            descriptorFile.setReadable(true, false);
            descriptorFile.setExecutable(true, false);
        } catch (IOException ex) {
            throw new MojoExecutionException("Unable to copy additional script: " + descriptorFile, ex);
        }

        // run the assembly plugin
        executeMojo(
                plugin(groupId("org.apache.maven.plugins"), artifactId("maven-assembly-plugin"), version("2.4")),
                goal("single"),
                configuration(element("descriptors", element("descriptor", descriptorFile.getAbsolutePath()))),
                executionEnvironment(project, mavenSession, mavenPluginManager));

        // delete the temp file
        descriptorFile.delete();
    }

}