Java tutorial
/* * 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; } } }