EclipseMojo.java

package org.andromda.maven.plugin.andromdapp;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.andromda.core.common.ResourceUtils;
import org.andromda.maven.plugin.andromdapp.eclipse.ClasspathWriter;
import org.andromda.maven.plugin.andromdapp.eclipse.ProjectWriter;
import org.andromda.maven.plugin.andromdapp.eclipse.Variable;
import org.andromda.maven.plugin.andromdapp.utils.ProjectUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
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.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Build;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.PluginManagement;
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.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.xml.Xpp3Dom;

/**
 * Writes the necessary .classpath and .project files
 * for a new eclipse application.
 *
 * @goal eclipse
 * @phase generate-sources
 * @author Chad Brandon
 */
public class EclipseMojo
    extends AbstractMojo
{
    /**
     * @parameter expression="${session}"
     */
    private MavenSession session;

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

    private static final String POM_FILE_NAME = "pom.xml";

    /**
     * Defines the POMs to include when generating the eclipse files.
     *
     * @parameter
     */
    private String[] includes = new String[] {"*/**/" + POM_FILE_NAME};

    /**
     * Defines the POMs to exclude when generating the eclipse files.
     *
     * @parameter expression="${exclude.poms}"
     */
    private String excludePoms;

    /**
     * Defines the POMs to exclude when generating the eclipse files.
     *
     * @parameter
     */
    private String[] excludes = new String[0];

    /**
     * Used to construct Maven project instances from POMs.
     *
     * @component
     */
    private MavenProjectBuilder projectBuilder;

    /**
     * The name of the variable that will store the maven repository location.
     *
     * @parameter expression="${repository.variable.name}
     */
    private String repositoryVariableName = "M2_REPO";

    /**
     * Artifact factory, needed to download source jars for inclusion in classpath.
     *
     * @component role="org.apache.maven.artifact.factory.ArtifactFactory"
     * @required
     * @readonly
     */
    private ArtifactFactory artifactFactory;

    /**
     * Artifact resolver, needed to download source jars for inclusion in classpath.
     *
     * @component role="org.apache.maven.artifact.resolver.ArtifactResolver"
     * @required
     * @readonly
     */
    private ArtifactResolver artifactResolver;

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

    /**
     * @parameter
     */
    protected Variable[] variables;

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

    /**
     * The artifact types which should be included in the generated Eclipse classpath.
     *
     * @parameter
     */
    private Set<String> classpathArtifactTypes = new LinkedHashSet<String>(Arrays.asList("jar","ejb"));

    /**
     * Whether or not transitive dependencies shall be included in any resources (i.e. .classpath
     * that are generated by this mojo).
     *
     * @parameter expression="${resolveTransitiveDependencies}"
     */
    private boolean resolveTransitiveDependencies = true;

    /**
     * Allows non-generated configuration to be "merged" into the generated .classpath file.
     *
     * @parameter
     */
    private String classpathMerge;

    /**
     * Whether or not processing should be skipped (this is if you just want to force AndroMDA
     * not to run on your model).
     *
     * @parameter expression="${andromda.run.skip}"
     */
    private boolean skipProcessing = false;

    /**
     * @see org.apache.maven.plugin.Mojo#execute()
     */
    public void execute()
        throws MojoExecutionException
    {
        if (!this.skipProcessing)
        {
            try
            {
                final MavenProject rootProject = this.getRootProject();
                final ProjectWriter projectWriter = new ProjectWriter(rootProject, this.getLog());
                projectWriter.write();
                final Map<MavenProject, Collection<String>> originalCompileSourceRoots = this.collectProjectCompileSourceRoots();
                final List<MavenProject> projects = this.collectProjects();
                this.processCompileSourceRoots(projects);
                final ClasspathWriter classpathWriter = new ClasspathWriter(rootProject, this.getLog());
                classpathWriter.write(
                    projects,
                    this.repositoryVariableName,
                    this.artifactFactory,
                    this.artifactResolver,
                    this.localRepository,
                    this.artifactMetadataSource,
                    this.classpathArtifactTypes,
                    this.project.getRemoteArtifactRepositories(),
                    this.resolveTransitiveDependencies,
                    this.variables,
                    this.classpathMerge);
                // - reset to the original source roots
                for (final MavenProject project : projects)
                {
                    project.getCompileSourceRoots().clear();
                    project.getCompileSourceRoots().addAll(originalCompileSourceRoots.get(project));
                }
            }
            catch (Throwable throwable)
            {
                throw new MojoExecutionException("Error creating eclipse configuration", throwable);
            }
        }
    }

    /**
     * Collects all existing project compile source roots.
     *
     * @return a collection of collections
     */
    private Map<MavenProject, Collection<String>> collectProjectCompileSourceRoots()
        throws Exception
    {
        final Map<MavenProject, Collection<String>> sourceRoots = new LinkedHashMap<MavenProject, Collection<String>>();
        for (final MavenProject project : this.collectProjects())
        {
            sourceRoots.put(project, new ArrayList<String>(project.getCompileSourceRoots()));
        }
        return sourceRoots;
    }

    private List<MavenProject> projects = new ArrayList<MavenProject>();

    /**
     * Collects all projects from all POMs within the current project.
     *
     * @return all applicable Maven project instances.
     *
     * @throws MojoExecutionException
     */
    private List<MavenProject> collectProjects()
        throws Exception
    {
        if (projects.isEmpty())
        {
            final List<File> poms = this.getPoms();
            for (File pom : poms)
            {
                try
                {
                    // - first attempt to get the existing project from the session
                    final MavenProject project = ProjectUtils.getProject(this.projectBuilder, this.session, pom, this.getLog());
                    if (project != null)
                    {
                        this.getLog().info("found project " + project.getId());
                        projects.add(project);
                    }
                    else
                    {
                        if (this.getLog().isWarnEnabled())
                        {
                            this.getLog().warn("Could not load project from pom: " + pom + " - ignoring");
                        }
                    }
                }
                catch (ProjectBuildingException exception)
                {
                    throw new MojoExecutionException("Error loading " + pom, exception);
                }
            }
        }
        return projects;
    }

    /**
     * Processes the project compile source roots (adds all appropriate ones to the projects)
     * so that they're available to the eclipse mojos.
     *
     * @param projects the projects to process.
     * @throws Exception
     */
    private void processCompileSourceRoots(final List<MavenProject> projects)
        throws Exception
    {
        for (final MavenProject project : projects)
        {
            final Set<String> compileSourceRoots = new LinkedHashSet<String>(project.getCompileSourceRoots());
            compileSourceRoots.addAll(this.getExtraSourceDirectories(project));
            final String testSourceDirectory = project.getBuild().getTestSourceDirectory();
            if (StringUtils.isNotBlank(testSourceDirectory))
            {
                compileSourceRoots.add(testSourceDirectory);
            }
            project.getCompileSourceRoots().clear();
            project.getCompileSourceRoots().addAll(compileSourceRoots);
        }
    }

    /**
     * The artifact id for the multi source plugin.
     */
    private static final String MULTI_SOURCE_PLUGIN_ARTIFACT_ID = "build-helper-maven-plugin";

    /**
     * Retrieves any additional source directories which are defined within the build-helper-maven-plugin.
     *
     * @param project the maven project from which to retrieve the extra source directories.
     * @return the list of extra source directories.
     */
    private List<String> getExtraSourceDirectories(final MavenProject project)
    {
        final List<String> sourceDirectories = new ArrayList<String>();
        final Build build = project.getBuild();
        if (build != null)
        {
            final PluginManagement pluginManagement = build.getPluginManagement();
            if (pluginManagement != null && !pluginManagement.getPlugins().isEmpty())
            {
                Plugin multiSourcePlugin = null;
                for (final Plugin plugin : pluginManagement.getPlugins())
                {
                    if (MULTI_SOURCE_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId()))
                    {
                        multiSourcePlugin = plugin;
                        break;
                    }
                }
                final Xpp3Dom configuration = this.getConfiguration(multiSourcePlugin);
                if (configuration != null && configuration.getChildCount() > 0)
                {
                    final Xpp3Dom directories = configuration.getChild(0);
                    if (directories != null)
                    {
                        final int childCount = directories.getChildCount();
                        if (childCount > 0)
                        {
                            final String baseDirectory =
                                ResourceUtils.normalizePath(ObjectUtils.toString(project.getBasedir()) + '/');
                            final Xpp3Dom[] children = directories.getChildren();
                            for (int ctr = 0; ctr < childCount; ctr++)
                            {
                                final Xpp3Dom child = children[ctr];
                                if (child != null)
                                {
                                    String directoryValue = ResourceUtils.normalizePath(child.getValue());
                                    if (directoryValue != null)
                                    {
                                        if (!directoryValue.startsWith(baseDirectory))
                                        {
                                            directoryValue =
                                                ResourceUtils.normalizePath(baseDirectory + directoryValue.trim());
                                        }
                                        sourceDirectories.add(directoryValue);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return sourceDirectories;
    }

    /**
     * Retrieves the appropriate configuration instance (first tries
     * to get the configuration from the plugin, then tries from the plugin's
     * executions.
     *
     * @param plugin the plugin from which the retrieve the configuration.
     * @return the plugin's configuration, or null if not found.
     */
    private Xpp3Dom getConfiguration(final Plugin plugin)
    {
        Xpp3Dom configuration = null;
        if (plugin != null)
        {
            if (plugin.getConfiguration() != null)
            {
                configuration = (Xpp3Dom)plugin.getConfiguration();
            }
            else
            {
                final List<PluginExecution> executions = plugin.getExecutions();
                if (executions != null && !executions.isEmpty())
                {
                    // - there should only be one execution so we get the first one
                    final PluginExecution execution = plugin.getExecutions().iterator().next();
                    configuration = (Xpp3Dom)execution.getConfiguration();
                }
            }
        }
        return configuration;
    }

    /**
     * Stores the root project.
     */
    private MavenProject rootProject;

    /**
     * Retrieves the root project (i.e. the root parent project)
     * for this project.
     *
     * @return the root project.
     * @throws MojoExecutionException
     */
    private MavenProject getRootProject()
        throws MojoExecutionException, ArtifactResolutionException, ArtifactNotFoundException
    {
        if (this.rootProject == null)
        {
            final MavenProject firstParent = this.project.getParent();
            File rootFile = this.project.getFile();
            if (firstParent != null && firstParent.getFile() != null )
            {
                for (this.rootProject = firstParent, rootFile = new File(rootFile.getParentFile().getParentFile(), POM_FILE_NAME);
                     this.rootProject.getParent() != null && this.rootProject.getParent().getFile() != null;
                     this.rootProject = this.rootProject.getParent(), rootFile = new File(rootFile.getParentFile().getParentFile(), POM_FILE_NAME))
                {
                }
                // - if the project has no file defined, use the rootFile
                if (this.rootProject != null && this.rootProject.getFile() == null && rootFile.exists())
                {
                    this.rootProject.setFile(rootFile);
                }
            }
            else
            {
                this.rootProject = this.project;
            }
        }
        return this.rootProject;
    }

    /**
     * Retrieves all the POMs for the given project.
     *
     * @return all poms found.
     * @throws MojoExecutionException
     */
    private List<File> getPoms()
        throws Exception
    {
        final DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(this.getRootProject().getBasedir());
        scanner.setIncludes(this.includes);

        final List<String> excludes = new ArrayList<String>(Arrays.asList(this.excludes));
        if (this.excludePoms != null)
        {
            excludes.addAll(Arrays.asList(excludePoms.split(",")));
        }
        scanner.setExcludes(excludes.toArray(new String[excludes.size()]));
        scanner.scan();

        List<File> poms = new ArrayList<File>();

        for (int ctr = 0; ctr < scanner.getIncludedFiles().length; ctr++)
        {
            final File file = new File(
                this.getRootProject().getBasedir(),
                scanner.getIncludedFiles()[ctr]);
            if (file.exists())
            {
                poms.add(file);
            }
        }

        return poms;
    }
}