View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.project;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Objects;
30  
31  import org.apache.maven.RepositoryUtils;
32  import org.apache.maven.artifact.Artifact;
33  import org.apache.maven.model.Dependency;
34  import org.apache.maven.model.DependencyManagement;
35  import org.apache.maven.model.Exclusion;
36  import org.codehaus.plexus.util.StringUtils;
37  import org.eclipse.aether.DefaultRepositorySystemSession;
38  import org.eclipse.aether.RepositorySystem;
39  import org.eclipse.aether.RepositorySystemSession;
40  import org.eclipse.aether.RequestTrace;
41  import org.eclipse.aether.artifact.ArtifactType;
42  import org.eclipse.aether.artifact.ArtifactTypeRegistry;
43  import org.eclipse.aether.collection.CollectRequest;
44  import org.eclipse.aether.collection.DependencyCollectionException;
45  import org.eclipse.aether.graph.DependencyFilter;
46  import org.eclipse.aether.graph.DependencyNode;
47  import org.eclipse.aether.graph.DependencyVisitor;
48  import org.eclipse.aether.resolution.ArtifactResult;
49  import org.eclipse.aether.resolution.DependencyRequest;
50  import org.eclipse.aether.util.artifact.ArtifactIdUtils;
51  import org.eclipse.aether.util.artifact.JavaScopes;
52  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  /**
57   * @author Benjamin Bentmann
58   */
59  @Named
60  @Singleton
61  public class DefaultProjectDependenciesResolver implements ProjectDependenciesResolver {
62      private final Logger logger = LoggerFactory.getLogger(getClass());
63      private final RepositorySystem repoSystem;
64      private final List<RepositorySessionDecorator> decorators;
65  
66      @Inject
67      public DefaultProjectDependenciesResolver(
68              RepositorySystem repoSystem, List<RepositorySessionDecorator> decorators) {
69          this.repoSystem = repoSystem;
70          this.decorators = decorators;
71      }
72  
73      public DependencyResolutionResult resolve(DependencyResolutionRequest request)
74              throws DependencyResolutionException {
75          final RequestTrace trace = RequestTrace.newChild(null, request);
76  
77          final DefaultDependencyResolutionResult result = new DefaultDependencyResolutionResult();
78  
79          final MavenProject project = request.getMavenProject();
80          final DependencyFilter filter = request.getResolutionFilter();
81          RepositorySystemSession session = request.getRepositorySession();
82          ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
83  
84          if (logger.isDebugEnabled()
85                  && session.getConfigProperties().get(DependencyManagerUtils.CONFIG_PROP_VERBOSE) == null) {
86              DefaultRepositorySystemSession verbose = new DefaultRepositorySystemSession(session);
87              verbose.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, Boolean.TRUE);
88              session = verbose;
89          }
90  
91          for (RepositorySessionDecorator decorator : decorators) {
92              RepositorySystemSession decorated = decorator.decorate(project, session);
93              if (decorated != null) {
94                  session = decorated;
95              }
96          }
97  
98          CollectRequest collect = new CollectRequest();
99          collect.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact()));
100         collect.setRequestContext("project");
101         collect.setRepositories(project.getRemoteProjectRepositories());
102 
103         if (project.getDependencyArtifacts() == null) {
104             for (Dependency dependency : project.getDependencies()) {
105                 if (StringUtils.isEmpty(dependency.getGroupId())
106                         || StringUtils.isEmpty(dependency.getArtifactId())
107                         || StringUtils.isEmpty(dependency.getVersion())) {
108                     // guard against case where best-effort resolution for invalid models is requested
109                     continue;
110                 }
111                 collect.addDependency(RepositoryUtils.toDependency(dependency, stereotypes));
112             }
113         } else {
114             Map<String, Dependency> dependencies = new HashMap<>();
115             for (Dependency dependency : project.getDependencies()) {
116                 String classifier = dependency.getClassifier();
117                 if (classifier == null) {
118                     ArtifactType type = stereotypes.get(dependency.getType());
119                     if (type != null) {
120                         classifier = type.getClassifier();
121                     }
122                 }
123                 String key = ArtifactIdUtils.toVersionlessId(
124                         dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(), classifier);
125                 dependencies.put(key, dependency);
126             }
127             for (Artifact artifact : project.getDependencyArtifacts()) {
128                 String key = artifact.getDependencyConflictId();
129                 Dependency dependency = dependencies.get(key);
130                 Collection<Exclusion> exclusions = dependency != null ? dependency.getExclusions() : null;
131                 org.eclipse.aether.graph.Dependency dep = RepositoryUtils.toDependency(artifact, exclusions);
132                 if (!JavaScopes.SYSTEM.equals(dep.getScope())
133                         && dep.getArtifact().getFile() != null) {
134                     // enable re-resolution
135                     org.eclipse.aether.artifact.Artifact art = dep.getArtifact();
136                     art = art.setFile(null).setVersion(art.getBaseVersion());
137                     dep = dep.setArtifact(art);
138                 }
139                 collect.addDependency(dep);
140             }
141         }
142 
143         DependencyManagement depMgmt = project.getDependencyManagement();
144         if (depMgmt != null) {
145             for (Dependency dependency : depMgmt.getDependencies()) {
146                 collect.addManagedDependency(RepositoryUtils.toDependency(dependency, stereotypes));
147             }
148         }
149 
150         DependencyRequest depRequest = new DependencyRequest(collect, filter);
151         depRequest.setTrace(trace);
152 
153         DependencyNode node;
154         try {
155             collect.setTrace(RequestTrace.newChild(trace, depRequest));
156             node = repoSystem.collectDependencies(session, collect).getRoot();
157             result.setDependencyGraph(node);
158         } catch (DependencyCollectionException e) {
159             result.setDependencyGraph(e.getResult().getRoot());
160             result.setCollectionErrors(e.getResult().getExceptions());
161 
162             throw new DependencyResolutionException(
163                     result, "Could not resolve dependencies for project " + project.getId() + ": " + e.getMessage(), e);
164         }
165 
166         depRequest.setRoot(node);
167 
168         if (logger.isWarnEnabled()) {
169             for (DependencyNode child : node.getChildren()) {
170                 if (!child.getRelocations().isEmpty()) {
171                     org.eclipse.aether.artifact.Artifact relocated =
172                             child.getDependency().getArtifact();
173                     String message = relocated instanceof org.apache.maven.repository.internal.RelocatedArtifact
174                             ? ((org.apache.maven.repository.internal.RelocatedArtifact) relocated).getMessage()
175                             : null;
176                     logger.warn("The artifact " + child.getRelocations().get(0) + " has been relocated to " + relocated
177                             + (message != null ? ": " + message : ""));
178                 }
179             }
180         }
181 
182         if (logger.isDebugEnabled()) {
183             node.accept(new GraphLogger(project));
184         }
185 
186         try {
187             process(result, repoSystem.resolveDependencies(session, depRequest).getArtifactResults());
188         } catch (org.eclipse.aether.resolution.DependencyResolutionException e) {
189             process(result, e.getResult().getArtifactResults());
190 
191             throw new DependencyResolutionException(
192                     result, "Could not resolve dependencies for project " + project.getId() + ": " + e.getMessage(), e);
193         }
194 
195         return result;
196     }
197 
198     private void process(DefaultDependencyResolutionResult result, Collection<ArtifactResult> results) {
199         for (ArtifactResult ar : results) {
200             DependencyNode node = ar.getRequest().getDependencyNode();
201             if (ar.isResolved()) {
202                 result.addResolvedDependency(node.getDependency());
203             } else {
204                 result.setResolutionErrors(node.getDependency(), ar.getExceptions());
205             }
206         }
207     }
208 
209     // Keep this class in sync with org.apache.maven.plugin.internal.DefaultPluginDependenciesResolver.GraphLogger
210     class GraphLogger implements DependencyVisitor {
211 
212         private final MavenProject project;
213 
214         private String indent = "";
215 
216         GraphLogger(MavenProject project) {
217             this.project = project;
218         }
219 
220         public boolean visitEnter(DependencyNode node) {
221             StringBuilder buffer = new StringBuilder(128);
222             buffer.append(indent);
223             org.eclipse.aether.graph.Dependency dep = node.getDependency();
224             if (dep != null) {
225                 org.eclipse.aether.artifact.Artifact art = dep.getArtifact();
226 
227                 buffer.append(art);
228                 if (StringUtils.isNotEmpty(dep.getScope())) {
229                     buffer.append(':').append(dep.getScope());
230                 }
231 
232                 if (dep.isOptional()) {
233                     buffer.append(" (optional)");
234                 }
235 
236                 // TODO We currently cannot tell which <dependencyManagement> section contained the management
237                 //      information. When the resolver provides this information, these log messages should be updated
238                 //      to contain it.
239                 if ((node.getManagedBits() & DependencyNode.MANAGED_SCOPE) == DependencyNode.MANAGED_SCOPE) {
240                     final String premanagedScope = DependencyManagerUtils.getPremanagedScope(node);
241                     buffer.append(" (scope managed from ");
242                     buffer.append(Objects.toString(premanagedScope, "default"));
243                     buffer.append(')');
244                 }
245 
246                 if ((node.getManagedBits() & DependencyNode.MANAGED_VERSION) == DependencyNode.MANAGED_VERSION) {
247                     final String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(node);
248                     buffer.append(" (version managed from ");
249                     buffer.append(Objects.toString(premanagedVersion, "default"));
250                     buffer.append(')');
251                 }
252 
253                 if ((node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == DependencyNode.MANAGED_OPTIONAL) {
254                     final Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(node);
255                     buffer.append(" (optionality managed from ");
256                     buffer.append(Objects.toString(premanagedOptional, "default"));
257                     buffer.append(')');
258                 }
259 
260                 if ((node.getManagedBits() & DependencyNode.MANAGED_EXCLUSIONS) == DependencyNode.MANAGED_EXCLUSIONS) {
261                     final Collection<org.eclipse.aether.graph.Exclusion> premanagedExclusions =
262                             DependencyManagerUtils.getPremanagedExclusions(node);
263 
264                     buffer.append(" (exclusions managed from ");
265                     buffer.append(Objects.toString(premanagedExclusions, "default"));
266                     buffer.append(')');
267                 }
268 
269                 if ((node.getManagedBits() & DependencyNode.MANAGED_PROPERTIES) == DependencyNode.MANAGED_PROPERTIES) {
270                     final Map<String, String> premanagedProperties =
271                             DependencyManagerUtils.getPremanagedProperties(node);
272 
273                     buffer.append(" (properties managed from ");
274                     buffer.append(Objects.toString(premanagedProperties, "default"));
275                     buffer.append(')');
276                 }
277             } else {
278                 buffer.append(project.getGroupId());
279                 buffer.append(':').append(project.getArtifactId());
280                 buffer.append(':').append(project.getPackaging());
281                 buffer.append(':').append(project.getVersion());
282             }
283 
284             logger.debug(buffer.toString());
285             indent += "   ";
286             return true;
287         }
288 
289         public boolean visitLeave(DependencyNode node) {
290             indent = indent.substring(0, indent.length() - 3);
291             return true;
292         }
293     }
294 }