org.grails.maven.plugin.AbstractGrailsMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.maven.plugin.AbstractGrailsMojo.java

Source

/*
 * Copyright 2007 the original author or authors.
 *
 * 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.
 */

package org.grails.maven.plugin;

import grails.util.Metadata;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import jline.Terminal;
import jline.TerminalFactory;
import jline.TerminalSupport;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.artifact.MavenMetadataSource;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.console.ConsoleLogger;
import org.grails.launcher.GrailsLauncher;
import org.grails.launcher.RootLoader;
import org.grails.maven.plugin.tools.GrailsServices;

/**
 * Common services for all Mojos using Grails
 *
 * @author <a href="mailto:aheritier@gmail.com">Arnaud HERITIER</a>
 * @author Peter Ledbrook
 * @author Jonathan Pearlin
 * @version $Id$
 */
public abstract class AbstractGrailsMojo extends AbstractMojo {

    public static final String PLUGIN_PREFIX = "grails-";

    private static final String GRAILS_PLUGIN_NAME_FORMAT = "plugins.%s:%s";
    public static final String APP_GRAILS_VERSION = "app.grails.version";

    /**
     * The directory where is launched the mvn command.
     *
     * @parameter default-value="${basedir}"
     * @required
     */
    protected File basedir;

    /**
     * The Grails environment to use.
     *
     * @parameter expression="${grails.env}"
     */
    protected String env;

    /**
     * Whether to run Grails in non-interactive mode or not. The default
     * is to run interactively, just like the Grails command-line.
     *
     * @parameter expression="${nonInteractive}" default-value="false"
     * @required
     */
    protected boolean nonInteractive;

    /**
     * The directory where plugins are stored.
     *
     * @parameter expression="${pluginsDirectory}" default-value="${basedir}/plugins"
     * @required
     */
    protected File pluginsDir;

    /**
     * The path to the Grails installation.
     *
     * @parameter expression="${grailsHome}"
     */
    protected File grailsHome;

    /** The Maven settings reference.
     *
     * @parameter expression="${settings}"
     * @required
     * @readonly
     */
    protected Settings settings;

    /**
     * POM
     *
     * @parameter expression="${project}"
     * @readonly
     * @required
     */
    protected MavenProject project;

    /**
     * @component
     */
    private ArtifactResolver artifactResolver;

    /**
     * @component
     */
    private ArtifactFactory artifactFactory;

    /**
     * @component
     */
    private ArtifactCollector artifactCollector;

    /**
     * @component
     */
    private ArtifactMetadataSource artifactMetadataSource;

    /**
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    private ArtifactRepository localRepository;

    /**
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    private List<?> remoteRepositories;

    /**
     * @component
     */
    private MavenProjectBuilder projectBuilder;

    /**
     * @component
     * @readonly
     */
    private GrailsServices grailsServices;

    /**
     * Returns the configured base directory for this execution of the plugin.
     * @return The base directory.
     */
    protected File getBasedir() {
        if (basedir == null) {
            throw new RuntimeException(
                    "Your subclass have a field called 'basedir'. Remove it and use getBasedir() " + "instead.");
        }

        return this.basedir;
    }

    /**
     * Returns the {@code GrailsServices} instance used by the plugin with the base directory
     * of the services object set to the configured base directory.
     * @return The underlying {@code GrailsServices} instance.
     */
    protected GrailsServices getGrailsServices() {
        grailsServices.setBasedir(basedir);
        return grailsServices;
    }

    /**
     * Executes the requested Grails target.  The "targetName" must match a known
     * Grails script provided by grails-scripts.
     * @param targetName The name of the Grails target to execute.
     * @throws MojoExecutionException if an error occurs while attempting to execute the target.
     */
    protected void runGrails(final String targetName) throws MojoExecutionException {
        runGrails(targetName, null);
    }

    /**
     * Executes the requested Grails target.  The "targetName" must match a known
     * Grails script provided by grails-scripts.
     * @param targetName The name of the Grails target to execute.
     * @param args String of arguments to be passed to the executed Grails target.
     * @throws MojoExecutionException if an error occurs while attempting to execute the target.
     */
    protected void runGrails(final String targetName, String args) throws MojoExecutionException {

        InputStream currentIn = System.in;
        PrintStream currentOutput = System.out;

        try {
            configureMavenProxy();

            final URL[] classpath = generateGrailsExecutionClasspath();

            final String grailsHomePath = (grailsHome != null) ? grailsHome.getAbsolutePath() : null;
            final RootLoader rootLoader = new RootLoader(classpath, ClassLoader.getSystemClassLoader());
            System.setProperty("grails.console.enable.terminal", "false");
            System.setProperty("grails.console.enable.interactive", "false");

            Class cls = rootLoader.loadClass("org.springframework.util.Log4jConfigurer");
            invokeStaticMethod(cls, "initLogging", new Object[] { "classpath:grails-maven/log4j.properties" });
            final GrailsLauncher launcher = new GrailsLauncher(rootLoader, grailsHomePath,
                    basedir.getAbsolutePath());
            launcher.setPlainOutput(true);
            configureBuildSettings(launcher);

            // Search for all Grails plugin dependencies and install
            // any that haven't already been installed.
            final Metadata metadata = Metadata.getInstance(new File(getBasedir(), "application.properties"));
            boolean metadataModified = syncGrailsVersion(metadata);

            for (@SuppressWarnings("unchecked")
            final Iterator<Artifact> iter = this.project.getDependencyArtifacts().iterator(); iter.hasNext();) {
                final Artifact artifact = iter.next();
                if (artifact.getType() != null
                        && (artifact.getType().equals("grails-plugin") || artifact.getType().equals("zip"))) {
                    metadataModified |= installGrailsPlugin(artifact, metadata, launcher);
                }
            }

            if (metadataModified)
                metadata.persist();

            // If the command is running in non-interactive mode, we
            // need to pass on the relevant argument.
            if (this.nonInteractive) {
                args = (args != null) ? "--non-interactive" + args : "--non-interactive ";
            }

            // Enable the plain output for the Grails command to fix an issue with JLine
            // consuming the standard output after execution via Maven.
            args = (args != null) ? "--plain-output " + args : "--plain-output";

            final int retval = launcher.launch(targetName, args, env);
            if (retval != 0) {
                throw new MojoExecutionException("Grails returned non-zero value: " + retval);
            }
        } catch (final MojoExecutionException ex) {
            // Simply rethrow it.
            throw ex;
        } catch (final Exception ex) {
            throw new MojoExecutionException("Unable to start Grails", ex);
        } finally {
            System.setIn(currentIn);
            System.setOut(currentOutput);
        }
    }

    private boolean syncGrailsVersion(Metadata metadata) {
        Object grailsVersion = metadata.get(APP_GRAILS_VERSION);

        Artifact grailsDependency = findGrailsDependency(project);
        if (grailsDependency != null) {
            if (!grailsDependency.getVersion().equals(grailsVersion)) {
                metadata.put(APP_GRAILS_VERSION, grailsDependency.getVersion());
                return true;
            }
        }
        return false;
    }

    private Artifact findGrailsDependency(MavenProject project) {
        Set dependencyArtifacts = project.getDependencyArtifacts();
        for (Object o : dependencyArtifacts) {
            Artifact artifact = (Artifact) o;
            if (artifact.getArtifactId().equals("grails-dependencies")) {
                return artifact;
            }
        }
        return null;
    }

    private void configureMavenProxy() {
        if (settings != null) {
            Proxy activeProxy = settings.getActiveProxy();
            if (activeProxy != null) {
                String host = activeProxy.getHost();
                int port = activeProxy.getPort();
                String username = activeProxy.getUsername();
                String password = activeProxy.getPassword();

                System.setProperty("http.proxyHost", host);
                System.setProperty("http.proxyPort", String.valueOf(port));
                if (username != null) {
                    System.setProperty("http.proxyUser", username);
                }
                if (password != null) {
                    System.setProperty("http.proxyPassword", password);
                }
            }
        }
    }

    /**
     * Invokes the named method on a target object using reflection.
     * The method signature is determined by the classes of each argument.
     * @param target The object to call the method on.
     * @param name The name of the method to call.
     * @param args The arguments to pass to the method (may be an empty array).
     * @return The value returned by the method.
     */
    private Object invokeStaticMethod(Class target, String name, Object[] args) {
        Class<?>[] argTypes = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            argTypes[i] = args[i].getClass();
        }

        try {
            return target.getMethod(name, argTypes).invoke(target, args);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Generates the classpath to be used by the launcher to execute the requested Grails script.
     * @return An array of {@code URL} objects representing the dependencies required on the classpath to
     *    execute the selected Grails script.
     * @throws MojoExecutionException if an error occurs while attempting to resolve the dependencies and
     *    generate the classpath array.
     */
    @SuppressWarnings("unchecked")
    private URL[] generateGrailsExecutionClasspath() throws MojoExecutionException {
        try {
            final List<Dependency> unresolvedDependencies = new ArrayList<Dependency>();
            final Set<Artifact> resolvedArtifacts = new HashSet<Artifact>();

            /*
             * Get the Grails dependencies from the plugin's POM file first.
             */
            final MavenProject pluginProject = getPluginProject();

            /*
             * Add the plugin's dependencies and the project using the plugin's dependencies to the list
             * of unresolved dependencies.  This is done so they can all be resolved at the same time so
             * that we get the benefit of Maven's conflict resolution.
             */
            unresolvedDependencies.addAll(filterDependencies(pluginProject.getDependencies(), "org.grails"));
            unresolvedDependencies.addAll(this.project.getDependencies());

            /*
             * Convert the Maven dependencies into Maven artifacts so that they can be resolved.
             */
            final List<Artifact> unresolvedArtifacts = dependenciesToArtifacts(unresolvedDependencies);

            /*
             * Resolve each artifact.  This will get all transitive artifacts AND eliminate conflicts.
             */
            for (Artifact unresolvedArtifact : unresolvedArtifacts) {
                resolvedArtifacts
                        .addAll(resolveDependenciesToArtifacts(unresolvedArtifact, unresolvedDependencies));
            }

            /*
             * Remove any Grails plugins that may be in the resolved artifact set.  This is because we
             * do not need them on the classpath, as they will be handled later on by a separate call to
             * "install" them.
             */
            removePluginArtifacts(resolvedArtifacts);

            /*
             * Convert each resolved artifact into a URL/classpath element.
             */
            final List<URL> classpath = new ArrayList<URL>();
            int index = 0;
            for (Artifact resolvedArtifact : resolvedArtifacts) {
                final File file = resolvedArtifact.getFile();
                if (file != null) {
                    classpath.add(file.toURI().toURL());
                }
            }

            /*
             * Add the "tools.jar" to the classpath so that the Grails scripts can run native2ascii.
             * First assume that "java.home" points to a JRE within a JDK.  NOTE that this will not
             * provide a valid path on Mac OSX.  This is not a big deal, as the JDK on Mac OSX already
             * adds the required JAR's to the classpath.  This logic is really only for Windows/*Unix.
             */
            final String javaHome = System.getProperty("java.home");
            File toolsJar = new File(javaHome, "../lib/tools.jar");
            if (!toolsJar.exists()) {
                // The "tools.jar" cannot be found with that path, so
                // now try with the assumption that "java.home" points
                // to a JDK.
                toolsJar = new File(javaHome, "tools.jar");
            }
            if (toolsJar.exists()) {
                java.net.URL url = toolsJar.toURI().toURL();
                if (url != null) {
                    classpath.add(url);
                }
            }
            return classpath.toArray(new URL[classpath.size()]);
        } catch (final Exception e) {
            throw new MojoExecutionException("Failed to create classpath for Grails execution.", e);
        }
    }

    private MavenProject getPluginProject() throws ProjectBuildingException {
        final Artifact pluginArtifact = findArtifact(this.project.getPluginArtifacts(), "org.grails",
                "grails-maven-plugin");
        return this.projectBuilder.buildFromRepository(pluginArtifact, this.remoteRepositories,
                this.localRepository);
    }

    /**
     * Returns only the dependencies matching the supplied group ID value, filtering out
     * all others.
     * @param dependencies A list of dependencies to be filtered.
     * @param groupId The group ID of the requested dependencies.
     * @return The filtered list of dependencies.
     */
    private List<Dependency> filterDependencies(final List<Dependency> dependencies, final String groupId) {
        final List<Dependency> filteredDependencies = new ArrayList<Dependency>();
        for (final Dependency dependency : dependencies) {
            if (dependency.getGroupId().equals(groupId)) {
                filteredDependencies.add(dependency);
            }
        }
        return filteredDependencies;
    }

    /**
     * Resolves the given Maven artifact (by getting its transitive dependencies and eliminating conflicts) against
     * the supplied list of dependencies.
     * @param artifact The artifact to be resolved.
     * @param dependencies The list of dependencies for the "project" (to aid with conflict resolution).
     * @return The resolved set of artifacts from the given artifact.  This includes the artifact itself AND its transitive artifacts.
     * @throws MojoExecutionException if an error occurs while attempting to resolve the artifact.
     */
    @SuppressWarnings("unchecked")
    private Set<Artifact> resolveDependenciesToArtifacts(final Artifact artifact,
            final List<Dependency> dependencies) throws MojoExecutionException {
        try {
            final MavenProject project = this.projectBuilder.buildFromRepository(artifact, this.remoteRepositories,
                    this.localRepository);

            //make Artifacts of all the dependencies
            final Set<Artifact> artifacts = MavenMetadataSource.createArtifacts(this.artifactFactory, dependencies,
                    null, null, null);

            final ArtifactResolutionResult result = artifactCollector.collect(artifacts, project.getArtifact(),
                    this.localRepository, this.remoteRepositories, this.artifactMetadataSource, null,
                    Collections.EMPTY_LIST);
            artifacts.addAll(result.getArtifacts());

            //not forgetting the Artifact of the project itself
            artifacts.add(project.getArtifact());

            //resolve all dependencies transitively to obtain a comprehensive list of assemblies
            for (final Iterator<Artifact> iter = artifacts.iterator(); iter.hasNext();) {
                final Artifact currentArtifact = iter.next();
                if (!currentArtifact.getArtifactId().equals("tools")
                        && !currentArtifact.getGroupId().equals("com.sun")) {
                    this.artifactResolver.resolve(currentArtifact, this.remoteRepositories, this.localRepository);
                }
            }

            return artifacts;
        } catch (final Exception ex) {
            throw new MojoExecutionException("Encountered problems resolving dependencies of the executable "
                    + "in preparation for its execution.", ex);
        }
    }

    /**
     * Configures the launcher for execution.
     * @param launcher The {@code GrailsLauncher} instance to be configured.
     */
    @SuppressWarnings("unchecked")
    private void configureBuildSettings(final GrailsLauncher launcher)
            throws ProjectBuildingException, MojoExecutionException {
        final String targetDir = this.project.getBuild().getDirectory();
        launcher.setDependenciesExternallyConfigured(true);
        launcher.setCompileDependencies(artifactsToFiles(this.project.getCompileArtifacts()));
        launcher.setTestDependencies(artifactsToFiles(this.project.getTestArtifacts()));
        launcher.setRuntimeDependencies(artifactsToFiles(this.project.getRuntimeArtifacts()));
        launcher.setProjectWorkDir(new File(targetDir));
        launcher.setClassesDir(new File(targetDir, "classes"));
        launcher.setTestClassesDir(new File(targetDir, "test-classes"));
        launcher.setResourcesDir(new File(targetDir, "resources"));
        launcher.setProjectPluginsDir(this.pluginsDir);

        final MavenProject pluginProject = getPluginProject();
        final List<Dependency> unresolvedDependencies = new ArrayList<Dependency>();
        final Set<Artifact> resolvedArtifacts = new HashSet<Artifact>();

        unresolvedDependencies.addAll(filterDependencies(pluginProject.getDependencies(), "org.grails"));

        /*
        * Convert the Maven dependencies into Maven artifacts so that they can be resolved.
        */
        final List<Artifact> unresolvedArtifacts = dependenciesToArtifacts(unresolvedDependencies);

        /*
        * Resolve each artifact.  This will get all transitive artifacts AND eliminate conflicts.
        */
        for (Artifact unresolvedArtifact : unresolvedArtifacts) {
            resolvedArtifacts.addAll(resolveDependenciesToArtifacts(unresolvedArtifact, unresolvedDependencies));
        }
        List<File> files = artifactsToFiles(resolvedArtifacts);
        launcher.setBuildDependencies(files);
    }

    /**
     * Installs a Grails plugin into the current project if it isn't
     * already installed. It works by simply unpacking the plugin
     * artifact (a ZIP file) into the appropriate location and adding
     * the plugin to the application's metadata.
     * @param plugin The plugin artifact to install.
     * @param metadata The application metadata. An entry for the plugin
     * is added to this if the installation is successful.
     * @param launcher The launcher instance that contains information about
     * the various project directories. In particular, this is where the
     * method gets the location of the project's "plugins" directory
     * from.
     * @return <code>true</code> if the plugin is installed and the
     * metadata updated, otherwise <code>false</code>.
     * @throws IOException
     * @throws ArchiverException
     */
    private boolean installGrailsPlugin(final Artifact plugin, final Metadata metadata,
            final GrailsLauncher launcher) throws IOException, ArchiverException {
        String pluginName = plugin.getArtifactId();
        final String pluginVersion = plugin.getVersion();

        if (pluginName.startsWith(PLUGIN_PREFIX)) {
            pluginName = pluginName.substring(PLUGIN_PREFIX.length());
        }
        getLog().info("Installing plugin " + pluginName + ":" + pluginVersion);

        // The directory the plugin will be unzipped to.
        final File targetDir = new File(launcher.getProjectPluginsDir(), pluginName + "-" + pluginVersion);

        // Unpack the plugin if it hasn't already been.
        if (!targetDir.exists()) {
            targetDir.mkdirs();

            final ZipUnArchiver unzipper = new ZipUnArchiver();
            unzipper.enableLogging(new ConsoleLogger(Logger.LEVEL_ERROR, "zip-unarchiver"));
            unzipper.setSourceFile(plugin.getFile());
            unzipper.setDestDirectory(targetDir);
            unzipper.setOverwrite(true);
            unzipper.extract();

            // Now add it to the application metadata.
            getLog().debug("Updating project metadata");
            metadata.setProperty(String.format(GRAILS_PLUGIN_NAME_FORMAT, plugin.getGroupId(), pluginName),
                    pluginVersion);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Converts a collection of Maven artifacts to files.  For this method to function properly,
     * the artifacts MUST be resolved first.
     * @param artifacts A collection of artifacts.
     * @return The list of files pointed to by the artifacts.
     */
    private List<File> artifactsToFiles(final Collection<Artifact> artifacts) {
        final List<File> files = new ArrayList<File>(artifacts.size());
        for (Artifact artifact : artifacts) {
            files.add(artifact.getFile());
        }

        return files;
    }

    /**
     * Finds the requested artifact in the supplied artifact collection.
     * @param artifacts A collection of artifacts.
     * @param groupId The group ID of the artifact to be found.
     * @param artifactId The artifact ID of the artifact to be found.
     * @return The artifact from the collection that matches the group ID and
     *    artifact ID value or {@code null} if no match is found.
     */
    private Artifact findArtifact(final Collection<Artifact> artifacts, final String groupId,
            final String artifactId) {
        for (final Artifact artifact : artifacts) {
            if (artifact.getGroupId().equals(groupId) && artifact.getArtifactId().equals(artifactId)) {
                return artifact;
            }
        }

        return null;
    }

    /**
     * Converts a collection of Dependency objects to a list of
     * corresponding Artifact objects.
     * @param deps The collection of dependencies to convert.
     * @return A list of Artifact instances.
     */
    private List<Artifact> dependenciesToArtifacts(final Collection<Dependency> deps) {
        final List<Artifact> artifacts = new ArrayList<Artifact>(deps.size());
        for (Dependency dep : deps) {
            artifacts.add(dependencyToArtifact(dep));
        }

        return artifacts;
    }

    /**
     * Uses the injected artifact factory to convert a single Dependency
     * object into an Artifact instance.
     * @param dep The dependency to convert.
     * @return The resulting Artifact.
     */
    private Artifact dependencyToArtifact(final Dependency dep) {
        return this.artifactFactory.createBuildArtifact(dep.getGroupId(), dep.getArtifactId(), dep.getVersion(),
                "pom");
    }

    /**
     * Removes any Grails plugin artifacts from the supplied list
     * of dependencies.  A Grails plugin is any artifact whose type
     * is equal to "grails-plugin" or "zip".
     * @param artifact The list of artifacts to be cleansed.
     */
    private void removePluginArtifacts(final Set<Artifact> artifact) {
        if (artifact != null) {
            for (final Iterator<Artifact> iter = artifact.iterator(); iter.hasNext();) {
                final Artifact dep = iter.next();
                if (dep.getType() != null
                        && (dep.getType().equals("grails-plugin") || dep.getType().equals("zip"))) {
                    iter.remove();
                }
            }
        }
    }
}