org.commonjava.emb.project.graph.DependencyGraphResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.commonjava.emb.project.graph.DependencyGraphResolver.java

Source

/*
 * Copyright 2010 Red Hat, Inc.
 * 
 * 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.commonjava.emb.project.graph;

import org.apache.log4j.Logger;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.commonjava.emb.project.ProjectToolsSession;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.artifact.ArtifactTypeRegistry;
import org.sonatype.aether.collection.CollectRequest;
import org.sonatype.aether.collection.CollectResult;
import org.sonatype.aether.collection.DependencyCollectionException;
import org.sonatype.aether.graph.DependencyNode;
import org.sonatype.aether.graph.DependencyVisitor;
import org.sonatype.aether.graph.Exclusion;
import org.sonatype.aether.repository.RemoteRepository;
import org.sonatype.aether.resolution.ArtifactRequest;
import org.sonatype.aether.resolution.ArtifactResolutionException;
import org.sonatype.aether.resolution.ArtifactResult;
import org.sonatype.aether.util.DefaultRepositorySystemSession;
import org.sonatype.aether.util.artifact.JavaScopes;
import org.sonatype.aether.util.graph.selector.ScopeDependencySelector;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Component(role = DependencyGraphResolver.class)
public class DependencyGraphResolver {

    private static final Logger LOGGER = Logger.getLogger(DependencyGraphResolver.class);

    @Requirement
    private RepositorySystem repositorySystem;

    public DependencyGraph resolveGraph(final Collection<MavenProject> rootProjects, RepositorySystemSession rss,
            final ProjectToolsSession session) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Preparing for dependency-graph accumulation...");
        }
        rss = prepareForGraphResolution(rss);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Accumulating dependency graph...");
        }
        final DependencyGraph depGraph = accumulate(session, rss, rootProjects,
                session.getRemoteRepositoriesArray());

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Resolving dependencies in graph...");
        }
        resolve(rss, rootProjects, depGraph);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Graph state contains: " + depGraph.size() + " nodes.");
        }

        return depGraph;
    }

    // TODO: Allow fine-tuning of scopes resolved...
    private RepositorySystemSession prepareForGraphResolution(final RepositorySystemSession s) {
        final DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(s);
        session.setDependencySelector(new ScopeDependencySelector());

        return session;
    }

    private void resolve(final RepositorySystemSession session, final Collection<MavenProject> rootProjects,
            final DependencyGraph depGraph) {
        final Set<DependencyResolveWorker> workers = new HashSet<DependencyResolveWorker>();
        for (final DepGraphNode node : depGraph) {
            if (node == null || node.hasErrors() || node.isPreResolved()) {
                continue;
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Resolving: " + node.getLatestArtifact());
            }
            workers.add(new DependencyResolveWorker(node, session, repositorySystem));
        }

        runResolve(workers);
        // for ( final DependencyResolveWorker worker : workers )
        // {
        // worker.run();
        // }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Dependency-graph resolution complete.");
        }
    }

    private void runResolve(final Set<DependencyResolveWorker> workers) {
        final ExecutorService executorService = Executors.newFixedThreadPool(1);

        final CountDownLatch latch = new CountDownLatch(workers.size());
        for (final DependencyResolveWorker worker : workers) {
            worker.setLatch(latch);
            executorService.execute(worker);
        }

        synchronized (latch) {
            long count = 0;
            while ((count = latch.getCount()) > 0) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(count + " resolution workers remaining. Waiting 3s...");
                }
                try {
                    latch.await(3, TimeUnit.SECONDS);
                } catch (final InterruptedException e) {
                    break;
                }
            }
        }

        boolean terminated = false;
        int count = 1;
        while (!terminated) {
            try {
                executorService.shutdown();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Attempt " + count + " to shutdown graph-resolver. Waiting 3s...");
                }

                count++;
                terminated = executorService.awaitTermination(3, TimeUnit.SECONDS);
            } catch (final InterruptedException e) {
                break;
            }
        }
    }

    private DependencyGraph accumulate(final ProjectToolsSession session, final RepositorySystemSession rss,
            final Collection<MavenProject> projects, final RemoteRepository... remoteRepositories) {
        final ArtifactTypeRegistry stereotypes = rss.getArtifactTypeRegistry();

        final DependencyGraph depGraph = session.getDependencyGraph();
        final GraphAccumulator accumulator = new GraphAccumulator(depGraph);

        for (final MavenProject project : projects) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Collecting dependencies for: " + project);
            }
            final CollectRequest request = new CollectRequest();
            request.setRequestContext("project");
            request.setRepositories(Arrays.asList(remoteRepositories));

            if (project.getDependencyArtifacts() == null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Adding dependencies to collection request...");
                }
                for (final Dependency dependency : project.getDependencies()) {
                    request.addDependency(RepositoryUtils.toDependency(dependency, stereotypes));
                }
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Mapping project dependencies by management key...");
                }
                final Map<String, Dependency> dependencies = new HashMap<String, Dependency>();
                for (final Dependency dependency : project.getDependencies()) {
                    final String key = dependency.getManagementKey();
                    dependencies.put(key, dependency);
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Adding dependencies to collection request...");
                }
                for (final org.apache.maven.artifact.Artifact artifact : project.getDependencyArtifacts()) {
                    final String key = artifact.getDependencyConflictId();
                    final Dependency dependency = dependencies.get(key);
                    final Collection<org.apache.maven.model.Exclusion> exclusions = dependency != null
                            ? dependency.getExclusions()
                            : null;

                    org.sonatype.aether.graph.Dependency dep = RepositoryUtils.toDependency(artifact, exclusions);
                    if (!JavaScopes.SYSTEM.equals(dep.getScope()) && dep.getArtifact().getFile() != null) {
                        // enable re-resolution
                        org.sonatype.aether.artifact.Artifact art = dep.getArtifact();
                        art = art.setFile(null).setVersion(art.getBaseVersion());
                        dep = dep.setArtifact(art);
                    }
                    request.addDependency(dep);
                }
            }

            final DependencyManagement depMngt = project.getDependencyManagement();
            if (depMngt != null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Adding managed dependencies to collection request...");
                }
                for (final Dependency dependency : depMngt.getDependencies()) {
                    request.addManagedDependency(RepositoryUtils.toDependency(dependency, stereotypes));
                }
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Collecting dependencies...");
            }
            CollectResult result;
            try {
                result = repositorySystem.collectDependencies(rss, request);
            } catch (final DependencyCollectionException e) {
                // TODO: Handle problem resolving POMs...
                result = e.getResult();

                // result.setDependencyGraph( e.getResult().getRoot() );
                // result.setCollectionErrors( e.getResult().getExceptions() );
                //
                // throw new DependencyResolutionException( result, "Could not resolve dependencies for project "
                // + project.getId() + ": " + e.getMessage(), e );
            }

            final DependencyNode root = result.getRoot();
            final DepGraphRootNode rootNode = depGraph.addRoot(root, project);

            accumulator.resetForNextRun(root, rootNode);

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Adding collected dependencies to consolidated dependency graph...");
            }
            result.getRoot().accept(accumulator);

        }

        return depGraph;
    }

    private static final class GraphAccumulator implements DependencyVisitor {
        private final LinkedList<DependencyNode> parents = new LinkedList<DependencyNode>();

        private final Set<Exclusion> exclusions = new HashSet<Exclusion>();

        private final Set<Exclusion> lastExclusions = new HashSet<Exclusion>();

        private final DependencyGraph depGraph;

        private DependencyNode root;

        private DepGraphRootNode rootNode;

        GraphAccumulator(final DependencyGraph depGraph) {
            this.depGraph = depGraph;
        }

        void resetForNextRun(final DependencyNode root, final DepGraphRootNode rootNode) {
            parents.clear();
            this.root = root;
            this.rootNode = rootNode;
            exclusions.clear();
            lastExclusions.clear();
        }

        @Override
        public boolean visitEnter(final DependencyNode node) {
            if (node == root) {
                parents.addFirst(root);
                return true;
            } else if (node == null || node.getDependency() == null || node.getDependency().getArtifact() == null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Invalid node: " + node);
                }
                return true;
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("START: dependency-processing for: " + node);
            }

            boolean result = false;
            final Artifact artifact = node.getDependency().getArtifact();
            if (!excluded(artifact)) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Enabling resolution for: " + node);
                }

                final DependencyNode parent = parents.getFirst();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Adding dependency from: " + parent + " to: " + node);
                }

                // TODO: don't traverse beyond this node if it's already been considered...though we still need to
                // connect it
                // to the parent node (see below).
                result = !depGraph.contains(node);

                // result = true;

                if (parent == root) {
                    depGraph.addDependency(rootNode, node);
                } else {
                    depGraph.addDependency(parent, node);
                }

                if (node.getDependency().getExclusions() != null) {
                    for (final Exclusion exclusion : node.getDependency().getExclusions()) {
                        if (exclusions.add(exclusion)) {
                            lastExclusions.add(exclusion);
                        }
                    }
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Pushing node: " + node + " onto parents stack.");
                }
                parents.addFirst(node);

                final StringBuilder builder = new StringBuilder();
                for (int i = 0; i < parents.size(); i++) {
                    builder.append("  ");
                }
                builder.append(">>>");
                builder.append(node);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(builder.toString());
                }
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("DISABLING resolution for: " + node);
                }
            }

            if (node != null && !node.getRelocations().isEmpty()) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("The artifact " + node.getRelocations().get(0) + " has been relocated to "
                            + node.getDependency().getArtifact());
                }
            }

            return result;
        }

        private boolean excluded(final Artifact artifact) {
            for (final Exclusion exclusion : exclusions) {
                if (match(exclusion.getGroupId(), artifact.getGroupId())
                        && match(exclusion.getArtifactId(), artifact.getArtifactId())
                        && match(exclusion.getExtension(), artifact.getExtension())
                        && match(exclusion.getClassifier(), artifact.getClassifier())) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("EXCLUDED: " + artifact);
                    }
                    return true;
                }
            }

            return false;
        }

        private boolean match(final String excluded, final String check) {
            return "*".equals(excluded) || excluded.equals(check);
        }

        @Override
        public boolean visitLeave(final DependencyNode node) {
            if (node == null || parents.isEmpty()) {
                return true;
            }

            if (node == parents.getFirst()) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Removing exclusions from last node: " + node);
                }

                for (final Exclusion exclusion : lastExclusions) {
                    exclusions.remove(exclusion);
                }

                lastExclusions.clear();

                final StringBuilder builder = new StringBuilder();
                for (int i = 0; i < parents.size(); i++) {
                    builder.append("  ");
                }
                builder.append("<<<");
                builder.append(node);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(builder.toString());
                }

                parents.removeFirst();
            } else {
                final int idx = parents.indexOf(node);
                if (idx > -1) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("TRAVERSAL LEAK. Removing " + (idx + 1)
                                + " unaccounted-for parents that have finished traversal.");
                    }

                    for (int i = 0; i <= idx; i++) {
                        parents.removeFirst();
                    }
                }
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("END: dependency-processing for: " + node);
            }

            return true;
        }

    }

    private static final class DependencyResolveWorker implements Runnable {

        private final DepGraphNode depState;

        private final RepositorySystemSession session;

        private final RepositorySystem repositorySystem;

        private ArtifactResult result;

        private CountDownLatch latch;

        DependencyResolveWorker(final DepGraphNode depState, final RepositorySystemSession session,
                final RepositorySystem repositorySystem) {
            this.depState = depState;
            this.session = session;
            this.repositorySystem = repositorySystem;
        }

        void setLatch(final CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {
            final Artifact artifact = depState.getLatestArtifact();
            try {
                final ArtifactRequest request = new ArtifactRequest(artifact,
                        new ArrayList<RemoteRepository>(depState.getRemoteRepositories()), "project");

                result = new ArtifactResult(request);
                if (validateForResolution()) {
                    try {
                        if (LOGGER.isDebugEnabled()) {
                            LOGGER.debug("RESOLVE: " + artifact);
                        }

                        result = repositorySystem.resolveArtifact(session, request);
                    } catch (final ArtifactResolutionException e) {
                        result.addException(e);
                    }
                }
            } finally {
                // FIXME: Do we need to detect whether resolution already happened for this artifact before we try to
                // resolve it
                // again??
                depState.merge(result);
                if (latch != null) {
                    latch.countDown();
                }
            }
        }

        private boolean validateForResolution() {
            boolean valid = true;
            if (session == null) {
                result.addException(new IllegalArgumentException("Cannot resolve dependency: "
                        + depState.getLatestArtifact() + ", RepositorySystemSession has not been set!"));

                valid = false;
            }

            if (repositorySystem == null) {
                result.addException(new IllegalArgumentException("Cannot resolve dependency: "
                        + depState.getLatestArtifact() + ", RepositorySystem has not been set!"));

                valid = false;
            }

            return valid;
        }
    }

}