com.azurenight.maven.TroposphereMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.azurenight.maven.TroposphereMojo.java

Source

package com.azurenight.maven;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 * 
 * 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.
 */
/*
 * Much code originally from jython-compile-maven-plugin project @
 * http://sourceforge.net/p/mavenjython/
 * Original author Johannes Buchner
 * 
 * Modified by Eduard Martinescu
 */

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.DirectoryScanner;

@Mojo(name = "tropo", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE)
public class TroposphereMojo extends AbstractMojo {
    private static final String SETUPTOOLS_EGG = "setuptools-0.6c11-py2.7.egg";
    private static final String BOTO_EGG = "boto-2.9.4-py2.7.egg";
    private static final String TROPO_EGG = "troposphere-0.3.4.1-py2.7.egg";

    private Artifact jythonArtifact;

    /**
     * Caching directory to download and build python packages, as well as
     * extracted jython dir
     * 
     */
    @Parameter(defaultValue = "target/troposphere-build-tmp", property = "tempDirectory")
    private File temporaryBuildDirectory;

    /**
     * The directory where the Python files (<code>*.tr</code>) are
     * located. Use of the '.tr' suffix instead of the customary '.py' 
     * is intentional to make it easy to include modules/other python code 
     * that is not converted by default.
     * All output generated by the '.tr' files in question are captured
     * and created as a cloud template with the same basename
     */
    @Parameter(defaultValue = "${basedir}/src/main/troposphere", property = "sourceDirectory")
    private File sourceDirectory;

    /**
     * The directory generated AWS cloud template files are stored.
     * The directory will be registered as a
     * compile source root of the project such that the generated files will
     * participate in later build phases like
     * compiling and packaging.
     * 
     */
    @Parameter(defaultValue = "${basedir}/src/cloud-templates", property = "outputDirectory")
    private File outputDirectory;

    /**
     * The granularity in milliseconds of the last modification date for testing
     * whether a source needs recompilation.
     * 
     */
    @Parameter(property = "lastModGranularityMs", defaultValue = "0")
    private int staleMillis;

    /**
     * A set of Ant-like inclusion patterns used to select files from the source
     * directory for processing. By default,
     * the patterns <code>**&#47;*.tr</code> and <code>**&#47;*.TR</code> are used
     * to select troposphere files.
     * 
     */
    @Parameter
    private String[] includes;

    /**
     * A set of Ant-like exclusion patterns used to prevent certain files from
     * being processed. By default, this set is
     * empty such that no files are excluded.
     * 
     */
    @Parameter
    private String[] excludes;

    /**
     * List of other Python modules that need to be installed prior to processing of your '.tr' files
     * Installation via easy install/setup tools
     * 
     */
    @Parameter(property = "libs")
    private List<String> libs;

    @Component
    private MavenProject project;

    /**
     * The setuptools jar resource
     */
    private URL setuptoolsResource;

    /**
     * The setuptools jar, once copied from the resource
     */
    private File setuptoolsJar;

    /**
     * URL & File to copy boto egg from within plugin
     */
    private URL botoURL;
    private File botoFile;

    /**
     * URL & File to copy troposphere egg
     */
    private URL tropoURL;
    private File tropoFile;

    /**
     * Lib/site-packages
     */
    private File sitepackagesdir;

    /**
     * Where packages are downloaded and built
     */
    private File packageDownloadCacheDir;

    /**
     * Lib/
     */
    private File libdir;

    /**
     * Should we override files during extraction if they already exist?
     * 
     * if true: will never work on tainted files; if false: will be faster.
     */
    private static final boolean OVERRIDE = false;

    protected String[] getIncludes() {
        if (this.includes != null) {
            return this.includes;
        } else {
            return new String[] { "**/*.tr", "**/*.TR" };
        }
    }

    protected String[] getExcludes() {
        return this.excludes;
    }

    protected File getOutputDirectory() {
        return this.outputDirectory;
    }

    protected int getStaleMillis() {
        return this.staleMillis;
    }

    protected File getSourceDirectory() {
        return this.sourceDirectory;
    }

    protected List<String> getLibraries() {
        return this.libs;
    }

    public void execute() throws MojoExecutionException {
        File sourceDirectory = getSourceDirectory();
        getLog().debug("source=" + sourceDirectory + " target=" + getOutputDirectory());
        if (!(sourceDirectory != null && sourceDirectory.exists())) {
            getLog().info("Request to add '" + sourceDirectory + "' folder. Not added since it does not exist.");
            return;
        }

        File f = outputDirectory;
        if (!f.exists()) {
            f.mkdirs();
        }
        setupVariables();

        extractJarToDirectory(jythonArtifact.getFile(), temporaryBuildDirectory);

        // now what? we have the jython content, now we need
        // easy_install
        getLog().debug("installing easy_install ...");
        try {
            FileUtils.copyInputStreamToFile(setuptoolsResource.openStream(), setuptoolsJar);
        } catch (IOException e) {
            throw new MojoExecutionException("copying setuptools failed", e);
        }
        try {
            FileUtils.copyInputStreamToFile(botoURL.openStream(), botoFile);
        } catch (IOException e) {
            throw new MojoExecutionException("copying boto failed", e);
        }
        try {
            FileUtils.copyInputStreamToFile(tropoURL.openStream(), tropoFile);
        } catch (IOException e) {
            throw new MojoExecutionException("copying troposphere failed", e);
        }
        extractJarToDirectory(setuptoolsJar, new File(sitepackagesdir, SETUPTOOLS_EGG));
        try {
            IOUtils.write("./" + SETUPTOOLS_EGG + "\n",
                    new FileOutputStream(new File(sitepackagesdir, "setuptools.pth")));
        } catch (IOException e) {
            throw new MojoExecutionException("writing path entry for setuptools failed", e);
        }
        getLog().debug("installing easy_install done");
        Iterator<String> it = libs.iterator();
        while (it.hasNext()) {
            String s = it.next();
            if (s == null || s.length() == 0) {
                it.remove();
            }
        }
        if (libs == null || libs.size() == 0) {
            getLog().info("no python libraries requested");
        } else {
            getLog().debug("installing requested python libraries");
            // then we need to call easy_install to install the other
            // dependencies.
            runJythonScriptOnInstall(temporaryBuildDirectory,
                    getEasyInstallArgs("Lib/site-packages/" + SETUPTOOLS_EGG + "/easy_install.py"), null);
            getLog().debug("installing requested python libraries done");
        }

        processFiles();
    }

    /**
     * @throws MojoExecutionException
     * 
     */
    private void processFiles() throws MojoExecutionException {
        DirectoryScanner ds = new DirectoryScanner();
        ds.setBasedir(getSourceDirectory());
        ds.setIncludes(getIncludes());
        ds.setExcludes(getExcludes());
        ds.addDefaultExcludes();
        ds.scan();
        String[] files = ds.getIncludedFiles();
        for (String file : files) {
            getLog().info("Processing file: " + file);
            File fullFile = new File(sourceDirectory, file);
            String destFile = file;
            if (FilenameUtils.indexOfExtension(file) > -1) {
                destFile = file.substring(0, FilenameUtils.indexOfExtension(file)) + ".template";
            }
            runJythonScriptOnInstall(temporaryBuildDirectory, getPythonArgs(fullFile.getAbsolutePath()),
                    new File(outputDirectory, destFile));
        }
    }

    /**
     * @param file
     * @return
     * @throws MojoExecutionException
     */
    private List<String> getPythonArgs(String file) throws MojoExecutionException {
        List<String> args = new ArrayList<String>();

        // I want to launch
        args.add("java");
        // to run the generated jython installation here
        args.add("-cp");
        args.add("." + getClassPathSeparator() + "Lib");
        // which should know about itself
        args.add("-Dpython.home=.");
        File jythonFakeExecutable = new File(temporaryBuildDirectory, "jython");
        try {
            jythonFakeExecutable.createNewFile();
        } catch (IOException e) {
            throw new MojoExecutionException("couldn't create file", e);
        }
        args.add("-Dpython.executable=" + jythonFakeExecutable.getName());
        args.add("org.python.util.jython");
        // and it should run the supplied file
        args.add(file);

        return args;
    }

    private void setupVariables() throws MojoExecutionException {
        jythonArtifact = findJythonArtifact();
        if (temporaryBuildDirectory == null) {
            temporaryBuildDirectory = new File("target/jython-plugins-tmp");
        }
        temporaryBuildDirectory.mkdirs();
        packageDownloadCacheDir = new File(temporaryBuildDirectory, "build");
        packageDownloadCacheDir.mkdir();
        libdir = new File(temporaryBuildDirectory, "Lib");
        if (!jythonArtifact.getFile().getName().endsWith(".jar")) {
            throw new MojoExecutionException(
                    "I expected " + jythonArtifact + " to provide a jar, but got " + jythonArtifact.getFile());
        }

        setuptoolsResource = getClass().getResource(SETUPTOOLS_EGG);
        if (setuptoolsResource == null)
            throw new MojoExecutionException("resource setuptools egg not found");
        setuptoolsJar = new File(packageDownloadCacheDir, SETUPTOOLS_EGG);
        sitepackagesdir = new File(libdir, "site-packages");

        botoURL = getClass().getResource(BOTO_EGG);
        botoFile = new File(packageDownloadCacheDir, BOTO_EGG);

        tropoURL = getClass().getResource(TROPO_EGG);
        tropoFile = new File(packageDownloadCacheDir, TROPO_EGG);

        if (libs == null) {
            getLog().info("libraries list empty");
            libs = new ArrayList<String>();
        }
        if (!libs.contains("boto")) {
            getLog().debug("missing boto library, adding automatically");
            libs.add(botoFile.getAbsolutePath());
        }
        if (!libs.contains("troposphere")) {
            getLog().debug("missing troposphere library, adding automatically");
            libs.add(tropoFile.getAbsolutePath());
        }
    }

    /**
     * @return
     * @throws MojoExecutionException
     */
    private Artifact findJythonArtifact() throws MojoExecutionException {
        for (Artifact i : project.getArtifacts()) {
            if (i.getArtifactId().equals("jython-standalone") && i.getGroupId().equals("org.python")) {
                return i;
            }
        }
        throw new MojoExecutionException("org.python.jython-standalone dependency not found. \n"
                + "Add a dependency to jython-standalone 2.7-b1 or newer to your project: \n" + " <dependency>\n"
                + "   <groupId>org.python</groupId>\n" + "   <artifactId>jython-standalone</artifactId>\n"
                + "   <version>2.7-b1</version>\n" + " </dependency>" + "\n");
    }

    public Collection<File> extractJarToDirectory(File jar, File outputDirectory) throws MojoExecutionException {
        getLog().debug("extracting " + jar);
        JarFile ja = openJarFile(jar);
        Enumeration<JarEntry> en = ja.entries();
        Collection<File> files = extractAllFiles(outputDirectory, ja, en);
        closeFile(ja);
        return files;
    }

    private JarFile openJarFile(File jar) throws MojoExecutionException {
        try {
            return new JarFile(jar);
        } catch (IOException e) {
            throw new MojoExecutionException("opening jython artifact jar failed", e);
        }
    }

    private void closeFile(ZipFile ja) throws MojoExecutionException {
        try {
            ja.close();
        } catch (IOException e) {
            throw new MojoExecutionException("closing jython artifact jar failed", e);
        }
    }

    private Collection<File> extractAllFiles(File outputDirectory, ZipFile ja, Enumeration<JarEntry> en)
            throws MojoExecutionException {
        List<File> files = new ArrayList<File>();
        while (en.hasMoreElements()) {
            JarEntry el = en.nextElement();
            if (!el.isDirectory()) {
                File destFile = new File(outputDirectory, el.getName());
                if (OVERRIDE || !destFile.exists()) {
                    destFile.getParentFile().mkdirs();
                    try {
                        FileOutputStream fo = new FileOutputStream(destFile);
                        IOUtils.copy(ja.getInputStream(el), fo);
                        fo.close();
                    } catch (IOException e) {
                        throw new MojoExecutionException(
                                "extracting " + el.getName() + " from jython artifact jar failed", e);
                    }
                }
                files.add(destFile);
            }
        }
        return files;
    }

    private List<String> getEasyInstallArgs(String easy_install_script) throws MojoExecutionException {
        List<String> args = new ArrayList<String>();

        // I want to launch
        args.add("java");
        // to run the generated jython installation here
        args.add("-cp");
        args.add("." + getClassPathSeparator() + "Lib");
        // which should know about itself
        args.add("-Dpython.home=.");
        //args.add("-Dpython.verbose=debug");
        File jythonFakeExecutable = new File(temporaryBuildDirectory, "jython");
        try {
            jythonFakeExecutable.createNewFile();
        } catch (IOException e) {
            throw new MojoExecutionException("couldn't create file", e);
        }
        args.add("-Dpython.executable=" + jythonFakeExecutable.getName());
        args.add("org.python.util.jython");
        args.add(easy_install_script);
        args.add("--always-unzip");
        args.add("--upgrade");
        args.add("--verbose");
        args.add("--build-directory");
        args.add(packageDownloadCacheDir.getAbsolutePath());
        // and install these libraries
        args.addAll(libs);

        return args;
    }

    private String getClassPathSeparator() {
        if (File.separatorChar == '\\')
            return ";";
        else
            return ":";
    }

    public void runJythonScriptOnInstall(File outputDirectory, List<String> args, File outputFile)
            throws MojoExecutionException {
        getLog().info("running " + args + " in " + outputDirectory);
        ProcessBuilder pb = new ProcessBuilder(args);
        pb.directory(outputDirectory);
        pb.environment().put("BASEDIR", project.getBasedir().getAbsolutePath());
        final Process p;
        ByteArrayOutputStream stdoutBaos = null;
        ByteArrayOutputStream stderrBaos = null;
        try {
            p = pb.start();
        } catch (IOException e) {
            throw new MojoExecutionException("Executing jython failed. tried to run: " + pb.command(), e);
        }
        if (outputFile == null) {
            stdoutBaos = new ByteArrayOutputStream();
            copyIO(p.getInputStream(), stdoutBaos);
        } else {
            try {
                copyIO(p.getInputStream(), new FileOutputStream(outputFile));
            } catch (FileNotFoundException e) {
                throw new MojoExecutionException("Failed to copy output to : " + outputFile.getAbsolutePath(), e);
            }
        }
        stderrBaos = new ByteArrayOutputStream();
        copyIO(p.getErrorStream(), stderrBaos);
        copyIO(System.in, p.getOutputStream());
        try {
            boolean error = false;
            if (p.waitFor() != 0) {
                error = true;
            }
            if (getLog().isDebugEnabled() && stdoutBaos != null) {
                getLog().debug(stdoutBaos.toString());
            }
            if (getLog().isErrorEnabled() && stderrBaos != null) {
                getLog().error(stderrBaos.toString());
            }
            if (error) {
                throw new MojoExecutionException("Jython failed with return code: " + p.exitValue());
            }
        } catch (InterruptedException e) {
            throw new MojoExecutionException("Python tests were interrupted", e);
        }

    }

    private void copyIO(final InputStream input, final OutputStream output) {
        new Thread(new Runnable() {

            public void run() {
                try {
                    IOUtils.copy(input, output);
                } catch (IOException e) {
                    getLog().error(e);
                }
            }
        }).start();

    }
}