org.commonjava.maven.plugins.betterdep.AbstractDepgraphGoal.java Source code

Java tutorial

Introduction

Here is the source code for org.commonjava.maven.plugins.betterdep.AbstractDepgraphGoal.java

Source

/*******************************************************************************
 * Copyright (c) 2014 Red Hat, Inc..
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.commonjava.maven.plugins.betterdep;

import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Profile;
import org.apache.maven.monitor.logging.DefaultLog;
import org.apache.maven.plugin.Mojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.logging.console.ConsoleLogger;
import org.commonjava.cartographer.CartoDataException;
import org.commonjava.cartographer.Cartographer;
import org.commonjava.cartographer.CartographerCoreBuilder;
import org.commonjava.cartographer.graph.discover.DiscoveryConfig;
import org.commonjava.cartographer.graph.discover.DiscoveryResult;
import org.commonjava.cartographer.graph.discover.patch.DepgraphPatcher;
import org.commonjava.cartographer.graph.preset.CommonPresetParameters;
import org.commonjava.cartographer.graph.preset.PresetSelector;
import org.commonjava.cartographer.request.GraphComposition;
import org.commonjava.cartographer.request.GraphDescription;
import org.commonjava.cartographer.spi.graph.discover.ProjectRelationshipDiscoverer;
import org.commonjava.maven.atlas.graph.RelationshipGraph;
import org.commonjava.maven.atlas.graph.RelationshipGraphException;
import org.commonjava.maven.atlas.graph.RelationshipGraphFactory;
import org.commonjava.maven.atlas.graph.ViewParams;
import org.commonjava.maven.atlas.graph.filter.ProjectRelationshipFilter;
import org.commonjava.maven.atlas.graph.mutate.ManagedDependencyMutator;
import org.commonjava.maven.atlas.graph.rel.BomRelationship;
import org.commonjava.maven.atlas.graph.rel.DependencyRelationship;
import org.commonjava.maven.atlas.graph.rel.ParentRelationship;
import org.commonjava.maven.atlas.graph.rel.ProjectRelationship;
import org.commonjava.maven.atlas.graph.rel.SimpleBomRelationship;
import org.commonjava.maven.atlas.graph.rel.SimpleDependencyRelationship;
import org.commonjava.maven.atlas.graph.rel.SimpleParentRelationship;
import org.commonjava.maven.atlas.graph.spi.RelationshipGraphConnectionFactory;
import org.commonjava.maven.atlas.graph.spi.neo4j.FileNeo4jConnectionFactory;
import org.commonjava.maven.atlas.graph.util.RelationshipUtils;
import org.commonjava.maven.atlas.ident.DependencyScope;
import org.commonjava.maven.atlas.ident.ref.ProjectRef;
import org.commonjava.maven.atlas.ident.ref.ProjectVersionRef;
import org.commonjava.maven.atlas.ident.ref.SimpleArtifactRef;
import org.commonjava.maven.atlas.ident.ref.SimpleProjectRef;
import org.commonjava.maven.atlas.ident.ref.SimpleProjectVersionRef;
import org.commonjava.maven.galley.model.Location;
import org.commonjava.maven.galley.model.SimpleLocation;
import org.commonjava.maven.plugins.betterdep.impl.MavenLocationExpander;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.apache.commons.lang.StringUtils.join;
import static org.commonjava.maven.atlas.ident.util.IdentityUtils.projectVersion;

/**
 * Abstract goal that takes care of setting up {@link Cartographer} and associated
 * instances/configuration, plus resolving dependency graphs, etc. Basic infrastructure
 * for all betterdep goals.
 * 
 * @author jdcasey
 */
public abstract class AbstractDepgraphGoal implements Mojo {

    public static final String WORKSPACE_ID = "betterdep";

    public static final String MUTATOR = "managed-dependency";

    /**
     * Write generated output to this file. Usually optional (except for 'repozip' 
     * goal, where it will default to 'target/repo.zip').
     */
    @Parameter(property = "output")
    protected File output;

    /**
     * List of projects currently being built by Maven. This list is optional.
     * 
     * If present, these projects will form the roots for the resolved dependency graph.
     * If empty, the -Dfrom=GAV[,GAV]* ({@link AbstractDepgraphGoal#fromProjects}) 
     * parameter will furnish the dependency graph roots instead.
     */
    @Parameter(defaultValue = "${reactorProjects}", readonly = true)
    private List<MavenProject> projects;

    /**
     * Use these GAVs as the roots of the dependency graph. If unspecified, and
     * this goal is run in the presence of actual on-disk projects, those will 
     * be used as the graph roots.
     */
    @Parameter(property = "from")
    private String fromProjects;

    /**
     * Specify a list of URLs from which to graph the dependency graph and any
     * artifacts needed to generate the goal's output.
     */
    @Parameter(property = "in")
    private String inRepos;

    /**
     * Temporary directory that will hold resolved POMs and other artifacts during
     * graph resolution and other activities.
     */
    // FIXME Explicit use of 'target/' is bad, but without a project available ${project.build.directory} doesn't graph.
    @Parameter(defaultValue = "target/dep/resolved", readonly = true, required = true)
    private File resolverDir;

    /**
     * Temporary directory used to store the resolved dependency graph. This can
     * speed up successive calls to betterdep goals if it is not erased between
     * invocations.
     */
    // FIXME Explicit use of 'target/' is bad, but without a project available ${project.build.directory} doesn't graph.
    @Parameter(defaultValue = "target/dep/db", readonly = true, required = true)
    private File dbDir;

    private Log log;

    /**
     * Scope for inclusion in the dependency graph.
     */
    @Parameter(defaultValue = "runtime", required = true, property = "scope")
    protected DependencyScope scope;

    /**
     * Whether to include managed dependencies in the output (other than BOMs, 
     * which are almost always included).
     */
    @Parameter(defaultValue = "false", property = "managed")
    protected boolean includeManaged;

    /**
     * Preset filter type to use when resolving the dependency graph and generating
     * output. Not normally used.
     */
    @Parameter(defaultValue = "betterdep", property = "preset")
    protected String preset;

    @Parameter(defaultValue = "${session}", readonly = true, required = true)
    private MavenSession session;

    /**
     * Whether to provide verbose output related to dependency graph resolution.
     */
    @Parameter(defaultValue = "false", property = "trace")
    protected boolean trace;

    protected Set<ProjectVersionRef> roots;

    protected ProjectRelationshipFilter filter;

    private Set<URI> profiles;

    protected RelationshipGraph graph;

    private Set<ProjectRelationship<?, ?>> rootRels;

    private ProjectRelationshipDiscoverer discoverer;

    private List<Location> customLocations;

    private List<ArtifactRepository> artifactRepositories;

    protected static CartographerCoreBuilder cartoBuilder;

    protected static Cartographer carto;

    protected static PresetSelector presets;

    private RelationshipGraphFactory graphFactory;

    public AbstractDepgraphGoal() {
        super();
    }

    protected void initDepgraph(final boolean useLocalRepo) throws MojoExecutionException {
        rootRels = new HashSet<ProjectRelationship<?, ?>>();
        profiles = new HashSet<URI>();

        if (inRepos != null) {
            final String[] repos = inRepos.split("\\s*,\\s*");
            customLocations = new ArrayList<Location>(repos.length);
            for (final String repo : repos) {
                customLocations.add(new SimpleLocation(repo, repo));
            }
        }

        artifactRepositories = session.getRequest().getRemoteRepositories();

        final List<String> activeProfiles = session.getRequest().getActiveProfiles();
        if (activeProfiles != null) {
            for (final String activeProfile : activeProfiles) {
                profiles.add(RelationshipUtils.profileLocation(activeProfile));
            }
        }

        if (carto == null) {
            startCartographer(useLocalRepo);
        }

        final Map<String, Object> presetParams = new HashMap<String, Object>();
        presetParams.put(CommonPresetParameters.SCOPE, scope);
        presetParams.put(CommonPresetParameters.MANAGED, Boolean.valueOf(includeManaged));

        filter = presets.getPresetFilter(preset, "betterdep", presetParams);

        if (fromProjects != null) {
            roots = toRefs(fromProjects);
            setupGraph();
            readFromGAVs();
        } else {
            roots = new LinkedHashSet<ProjectVersionRef>();
            readFromReactorProjects();
            setupGraph();
        }

        getLog().info("Got relationships:\n\n  " + join(rootRels, "\n  ") + "\n");

        if (profiles != null && !profiles.isEmpty()) {
            getLog().info("Activating pom locations:\n\n  " + join(profiles, "\n  ") + "\n");

            graph.getParams().addActivePomLocations(profiles);
        }

        storeRels(rootRels);
    }

    private void setupGraph() throws MojoExecutionException {
        try {
            graph = graphFactory
                    .open(new ViewParams(WORKSPACE_ID, filter, ManagedDependencyMutator.INSTANCE, roots), true);
        } catch (RelationshipGraphException e) {
            throw new MojoExecutionException("Failed to open base graph: " + e.getMessage(), e);
        }
    }

    protected void storeRels(final Set<ProjectRelationship<?, ?>> rels) throws MojoExecutionException {
        getLog().info("Storing direct relationships...");
        try {
            final Set<ProjectRelationship<?, ?>> rejected = graph.storeRelationships(rels);

            getLog().info("The following direct relationships were rejected:\n\n  " + join(rejected, "\n  ")
                    + "\n\n(" + (rels.size() - rejected.size()) + " were accepted)");
        } catch (final RelationshipGraphException e) {
            throw new MojoExecutionException(
                    "Failed to store direct project relationships in depgraph database: " + e.getMessage(), e);
        }
    }

    protected Writer getWriter() throws MojoExecutionException {
        if (output == null) {
            throw new MojoExecutionException("No output file specified. Cannot open output-file writer!");
        }

        output.getParentFile().mkdirs();
        try {
            return new FileWriter(output);
        } catch (final IOException e) {
            throw new MojoExecutionException("Failed to open output file: " + e.getMessage(), e);
        }
    }

    protected void write(final CharSequence cs) throws MojoExecutionException {
        if (output == null) {
            getLog().info(cs.toString());
        } else {
            try {
                FileUtils.write(output, cs.toString());
            } catch (final IOException e) {
                throw new MojoExecutionException(
                        "Failed to write output to file: " + output + ". Reason: " + e.getMessage(), e);
            }
        }
    }

    protected Set<ProjectVersionRef> toRefs(final String gavs) {
        final String[] rawGavs = gavs.split("\\s*,\\s*");
        final Set<ProjectVersionRef> refs = new HashSet<ProjectVersionRef>(rawGavs.length);
        for (final String rawGav : rawGavs) {
            refs.add(projectVersion(rawGav));
        }

        return refs;
    }

    protected void readFromGAVs() throws MojoExecutionException {
        getLog().info("Initializing direct graph relationships from projects: " + fromProjects);
        rootRels = getDirectRelsFor(roots);
    }

    protected Set<ProjectRelationship<?, ?>> getDirectRelsFor(final Set<ProjectVersionRef> refs)
            throws MojoExecutionException {
        final Collection<DepgraphPatcher> patchers = cartoBuilder.getDepgraphPatchers();
        final Set<String> patcherIds = new HashSet<String>();
        if (patchers != null) {
            for (final DepgraphPatcher depgraphPatcher : patchers) {
                patcherIds.add(depgraphPatcher.getId());
            }
        }

        final DiscoveryConfig config;
        try {
            config = new DiscoveryConfig(MavenLocationExpander.EXPANSION_TARGET);
            config.setEnabledPatchers(patcherIds);
            config.setStoreRelationships(true);
        } catch (final URISyntaxException e) {
            throw new MojoExecutionException(
                    "Cannot configure discovery for: " + refs + ". Try -X for more information.");
        }

        final Set<ProjectRelationship<?, ?>> rels = new HashSet<ProjectRelationship<?, ?>>();

        for (final ProjectVersionRef projectRef : refs) {
            if (discoverer == null) {
                discoverer = cartoBuilder.getDiscoverer();
            }

            DiscoveryResult result;
            try {
                result = discoverer.discoverRelationships(projectRef, graph, config);
            } catch (final CartoDataException e) {
                throw new MojoExecutionException(
                        "Cannot discover direct relationships for: " + projectRef + ": " + e.getMessage(), e);
            }

            if (result == null) {
                throw new MojoExecutionException("Cannot discover direct relationships for: " + projectRef
                        + ". Try -X for more information.");
            }

            for (final ProjectRelationship<?, ?> rel : result.getAcceptedRelationships()) {
                if (filter.accept(rel)) {
                    rels.add(rel);
                }
            }
        }

        return rels;
    }

    protected void readFromReactorProjects() throws MojoExecutionException {
        getLog().info("Initializing direct graph relationships from reactor projects: " + projects);
        for (final MavenProject project : projects) {
            final List<Profile> activeProfiles = project.getActiveProfiles();
            if (activeProfiles != null) {
                for (final Profile profile : activeProfiles) {
                    profiles.add(RelationshipUtils.profileLocation(profile.getId()));
                }
            }

            final ProjectVersionRef projectRef = new SimpleProjectVersionRef(project.getGroupId(),
                    project.getArtifactId(), project.getVersion());
            roots.add(projectRef);

            final List<Dependency> deps = project.getDependencies();
            //            final List<ProjectVersionRef> roots = new ArrayList<ProjectVersionRef>( deps.size() );

            URI localUri;
            try {
                localUri = new URI(MavenLocationExpander.LOCAL_URI);
            } catch (final URISyntaxException e) {
                throw new MojoExecutionException(
                        "Failed to construct dummy local URI to use as the source of the current project in the depgraph: "
                                + e.getMessage(),
                        e);
            }

            final int index = 0;
            for (final Dependency dep : deps) {
                final ProjectVersionRef depRef = new SimpleProjectVersionRef(dep.getGroupId(), dep.getArtifactId(),
                        dep.getVersion());

                //                roots.add( depRef );

                final List<Exclusion> exclusions = dep.getExclusions();
                final List<ProjectRef> excludes = new ArrayList<ProjectRef>();
                if (exclusions != null && !exclusions.isEmpty()) {
                    for (final Exclusion exclusion : exclusions) {
                        excludes.add(new SimpleProjectRef(exclusion.getGroupId(), exclusion.getArtifactId()));
                    }
                }

                rootRels.add(new SimpleDependencyRelationship(localUri, projectRef,
                        new SimpleArtifactRef(depRef, dep.getType(), dep.getClassifier(), dep.isOptional()),
                        DependencyScope.getScope(dep.getScope()), index, false,
                        excludes.toArray(new ProjectRef[excludes.size()])));
            }

            final DependencyManagement dm = project.getDependencyManagement();
            if (dm != null) {
                final List<Dependency> managed = dm.getDependencies();
                int i = 0;
                for (final Dependency dep : managed) {
                    if ("pom".equals(dep.getType()) && "import".equals(dep.getScope())) {
                        final ProjectVersionRef depRef = new SimpleProjectVersionRef(dep.getGroupId(),
                                dep.getArtifactId(), dep.getVersion());
                        rootRels.add(new SimpleBomRelationship(localUri, projectRef, depRef, i++));
                    }
                }
            }

            ProjectVersionRef lastParent = projectRef;
            MavenProject parent = project.getParent();
            while (parent != null) {
                final ProjectVersionRef parentRef = new SimpleProjectVersionRef(parent.getGroupId(),
                        parent.getArtifactId(), parent.getVersion());

                rootRels.add(new SimpleParentRelationship(localUri, projectRef, parentRef));

                lastParent = parentRef;
                parent = parent.getParent();
            }

            rootRels.add(new SimpleParentRelationship(lastParent));
        }

    }

    protected GraphComposition getComposition() {
        return getComposition(filter, roots);
    }

    protected GraphComposition getComposition(ProjectRelationshipFilter filter, Set<ProjectVersionRef> roots) {
        final GraphDescription graphDesc = new GraphDescription(filter, MUTATOR, roots);
        return new GraphComposition(null, Collections.singletonList(graphDesc));
    }

    protected Map<String, Set<ProjectVersionRef>> getLabelsMap() throws CartoDataException {
        final Map<String, Set<ProjectVersionRef>> labels = new HashMap<String, Set<ProjectVersionRef>>();
        labels.put("ROOT", roots);

        labels.put("NOT-RESOLVED", graph.getAllIncompleteSubgraphs());

        labels.put("VARIABLE", graph.getAllVariableSubgraphs());

        return labels;
    }

    private void startCartographer(final boolean useLocalRepo) throws MojoExecutionException {
        getLog().info("Starting cartographer...");
        try {
            resolverDir.mkdirs();
            dbDir.mkdirs();

            /* @formatter:off */
            // TODO: Create a proper cache provider that works with the maven local repository format.

            final MavenLocationExpander mavenLocations = new MavenLocationExpander(customLocations,
                    artifactRepositories, useLocalRepo ? session.getLocalRepository() : null);

            //            cartoBuilder = new CartographerBuilder( WORKSPACE_ID, resolverDir, 4, new JungWorkspaceFactory() )
            RelationshipGraphConnectionFactory connFactory = new FileNeo4jConnectionFactory(dbDir, true);
            graphFactory = new RelationshipGraphFactory(connFactory);

            cartoBuilder = new CartographerCoreBuilder(resolverDir, connFactory).withGraphFactory(graphFactory)
                    .withLocationExpander(mavenLocations).withSourceManager(mavenLocations).withDefaultTransports();

            carto = cartoBuilder.build();
            /* @formatter:on */

            presets = new PresetSelector();
        } catch (final CartoDataException e) {
            throw new MojoExecutionException("Failed to start cartographer: " + e.getMessage(), e);
        } catch (final MalformedURLException e) {
            throw new MojoExecutionException("Failed to start cartographer: " + e.getMessage(), e);
        } catch (final URISyntaxException e) {
            throw new MojoExecutionException("Failed to start cartographer: " + e.getMessage(), e);
        }
    }

    @Override
    public Log getLog() {
        if (log == null) {
            log = new DefaultLog(new ConsoleLogger());
        }

        return log;
    }

    @Override
    public void setLog(final Log log) {
        this.log = log;
    }

}