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.impl;
20  
21  import java.io.IOException;
22  import java.nio.file.Path;
23  import java.util.Collection;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.Set;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.function.Predicate;
30  import java.util.stream.Collectors;
31  
32  import org.apache.maven.api.Artifact;
33  import org.apache.maven.api.ArtifactCoordinates;
34  import org.apache.maven.api.DependencyCoordinates;
35  import org.apache.maven.api.DependencyScope;
36  import org.apache.maven.api.Node;
37  import org.apache.maven.api.PathScope;
38  import org.apache.maven.api.PathType;
39  import org.apache.maven.api.Project;
40  import org.apache.maven.api.RemoteRepository;
41  import org.apache.maven.api.Session;
42  import org.apache.maven.api.Version;
43  import org.apache.maven.api.annotations.Nonnull;
44  import org.apache.maven.api.annotations.Nullable;
45  import org.apache.maven.api.di.Named;
46  import org.apache.maven.api.di.Singleton;
47  import org.apache.maven.api.services.ArtifactResolver;
48  import org.apache.maven.api.services.ArtifactResolverException;
49  import org.apache.maven.api.services.ArtifactResolverResult;
50  import org.apache.maven.api.services.DependencyResolver;
51  import org.apache.maven.api.services.DependencyResolverException;
52  import org.apache.maven.api.services.DependencyResolverRequest;
53  import org.apache.maven.api.services.DependencyResolverResult;
54  import org.apache.maven.api.services.ProjectManager;
55  import org.eclipse.aether.DefaultRepositorySystemSession;
56  import org.eclipse.aether.RepositorySystemSession;
57  import org.eclipse.aether.collection.CollectRequest;
58  import org.eclipse.aether.collection.CollectResult;
59  import org.eclipse.aether.collection.DependencyCollectionException;
60  import org.eclipse.aether.graph.DependencyFilter;
61  import org.eclipse.aether.graph.DependencyNode;
62  import org.eclipse.aether.scope.ResolutionScope;
63  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
64  import org.eclipse.aether.util.graph.transformer.ConflictResolver;
65  
66  import static java.util.Objects.requireNonNull;
67  import static org.apache.maven.impl.ImplUtils.cast;
68  import static org.apache.maven.impl.ImplUtils.map;
69  
70  @Named
71  @Singleton
72  public class DefaultDependencyResolver implements DependencyResolver {
73  
74      /**
75       * Cache of information about the modules contained in a path element.
76       * Keys are the Java versions targeted by the project.
77       *
78       * <p><b>TODO:</b> This field should not be in this class, because the cache should be global to the session.
79       * This field exists here only temporarily, until clarified where to store session-wide caches.</p>
80       */
81      private final Map<Runtime.Version, PathModularizationCache> moduleCaches;
82  
83      /**
84       * Creates an initially empty resolver.
85       */
86      public DefaultDependencyResolver() {
87          // TODO: the cache should not be instantiated here, but should rather be session-wide.
88          moduleCaches = new ConcurrentHashMap<>();
89      }
90  
91      /**
92       * {@return the cache for the given request}.
93       *
94       * @param  request the request for which to get the target version
95       * @throws IllegalArgumentException if the version string cannot be interpreted as a valid version
96       */
97      private PathModularizationCache moduleCache(DependencyResolverRequest request) {
98          return moduleCaches.computeIfAbsent(getTargetVersion(request), PathModularizationCache::new);
99      }
100 
101     /**
102      * Returns the target version of the given request as a Java version object.
103      *
104      * @param  request the request for which to get the target version
105      * @return the target version as a Java object
106      * @throws IllegalArgumentException if the version string cannot be interpreted as a valid version
107      */
108     static Runtime.Version getTargetVersion(DependencyResolverRequest request) {
109         Version target = request.getTargetVersion();
110         return (target != null) ? Runtime.Version.parse(target.toString()) : Runtime.version();
111     }
112 
113     @Nonnull
114     @Override
115     public DependencyResolverResult collect(@Nonnull DependencyResolverRequest request)
116             throws DependencyResolverException, IllegalArgumentException {
117         requireNonNull(request, "request");
118         InternalSession session = InternalSession.from(request.getSession());
119         RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(session, request);
120         try {
121             Artifact rootArtifact;
122             DependencyCoordinates root;
123             Collection<DependencyCoordinates> dependencies;
124             Collection<DependencyCoordinates> managedDependencies;
125             List<RemoteRepository> remoteRepositories;
126             if (request.getProject().isPresent()) {
127                 Project project = request.getProject().get();
128                 rootArtifact = project.getPomArtifact();
129                 root = null;
130                 dependencies = project.getDependencies();
131                 managedDependencies = project.getManagedDependencies();
132                 remoteRepositories = request.getRepositories() != null
133                         ? request.getRepositories()
134                         : session.getService(ProjectManager.class).getRemoteProjectRepositories(project);
135             } else {
136                 rootArtifact = request.getRootArtifact().orElse(null);
137                 root = request.getRoot().orElse(null);
138                 dependencies = request.getDependencies();
139                 managedDependencies = request.getManagedDependencies();
140                 remoteRepositories =
141                         request.getRepositories() != null ? request.getRepositories() : session.getRemoteRepositories();
142             }
143             ResolutionScope resolutionScope = null;
144             if (request.getPathScope() != null) {
145                 resolutionScope = session.getSession()
146                         .getScopeManager()
147                         .getResolutionScope(request.getPathScope().id())
148                         .orElseThrow();
149             }
150             CollectRequest collectRequest = new CollectRequest()
151                     .setRootArtifact(rootArtifact != null ? session.toArtifact(rootArtifact) : null)
152                     .setRoot(root != null ? session.toDependency(root, false) : null)
153                     .setDependencies(session.toDependencies(dependencies, false))
154                     .setManagedDependencies(session.toDependencies(managedDependencies, true))
155                     .setRepositories(session.toResolvingRepositories(remoteRepositories))
156                     .setRequestContext(trace.context())
157                     .setTrace(trace.trace());
158             collectRequest.setResolutionScope(resolutionScope);
159 
160             RepositorySystemSession systemSession = session.getSession();
161             if (request.getVerbose()) {
162                 systemSession = new DefaultRepositorySystemSession(systemSession)
163                         .setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true)
164                         .setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true);
165             }
166 
167             try {
168                 final CollectResult result =
169                         session.getRepositorySystem().collectDependencies(systemSession, collectRequest);
170                 return new DefaultDependencyResolverResult(
171                         null,
172                         moduleCache(request),
173                         result.getExceptions(),
174                         session.getNode(result.getRoot(), request.getVerbose()),
175                         0);
176             } catch (DependencyCollectionException e) {
177                 String enhancedMessage = enhanceCollectionError(e, collectRequest);
178                 throw new DependencyResolverException(enhancedMessage, e);
179             }
180         } finally {
181             RequestTraceHelper.exit(trace);
182         }
183     }
184 
185     @Nonnull
186     @Override
187     public List<Node> flatten(@Nonnull Session s, @Nonnull Node node, @Nullable PathScope scope)
188             throws DependencyResolverException {
189         InternalSession session = InternalSession.from(s);
190         DependencyNode root = cast(AbstractNode.class, node, "node").getDependencyNode();
191         List<DependencyNode> dependencies = session.getRepositorySystem()
192                 .flattenDependencyNodes(session.getSession(), root, getScopeDependencyFilter(scope));
193         dependencies.remove(root);
194         return map(dependencies, session::getNode);
195     }
196 
197     private static DependencyFilter getScopeDependencyFilter(PathScope scope) {
198         if (scope == null) {
199             return null;
200         }
201         Set<String> scopes =
202                 scope.dependencyScopes().stream().map(DependencyScope::id).collect(Collectors.toSet());
203         return (n, p) -> {
204             org.eclipse.aether.graph.Dependency d = n.getDependency();
205             return d == null || scopes.contains(d.getScope());
206         };
207     }
208 
209     /**
210      * Collects, flattens and resolves the dependencies.
211      *
212      * @param request the request to resolve
213      * @return the result of the resolution
214      */
215     @Override
216     public DependencyResolverResult resolve(DependencyResolverRequest request)
217             throws DependencyResolverException, ArtifactResolverException {
218         InternalSession session =
219                 InternalSession.from(requireNonNull(request, "request").getSession());
220         RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(session, request);
221         DependencyResolverResult result;
222         try {
223             DependencyResolverResult collectorResult = collect(request);
224             List<RemoteRepository> repositories = request.getRepositories() != null
225                     ? request.getRepositories()
226                     : request.getProject().isPresent()
227                             ? session.getService(ProjectManager.class)
228                                     .getRemoteProjectRepositories(
229                                             request.getProject().get())
230                             : session.getRemoteRepositories();
231             if (request.getRequestType() == DependencyResolverRequest.RequestType.COLLECT) {
232                 result = collectorResult;
233             } else {
234                 List<Node> nodes = flatten(session, collectorResult.getRoot(), request.getPathScope());
235                 List<ArtifactCoordinates> coordinates = nodes.stream()
236                         .map(Node::getDependency)
237                         .filter(Objects::nonNull)
238                         .map(Artifact::toCoordinates)
239                         .collect(Collectors.toList());
240                 Predicate<PathType> filter = request.getPathTypeFilter();
241                 DefaultDependencyResolverResult resolverResult = new DefaultDependencyResolverResult(
242                         null,
243                         moduleCache(request),
244                         collectorResult.getExceptions(),
245                         collectorResult.getRoot(),
246                         nodes.size());
247                 if (request.getRequestType() == DependencyResolverRequest.RequestType.FLATTEN) {
248                     for (Node node : nodes) {
249                         resolverResult.addNode(node);
250                     }
251                 } else {
252                     ArtifactResolverResult artifactResolverResult =
253                             session.getService(ArtifactResolver.class).resolve(session, coordinates, repositories);
254                     for (Node node : nodes) {
255                         Path path = (node.getArtifact() != null)
256                                 ? artifactResolverResult
257                                         .getResult(node.getArtifact().toCoordinates())
258                                         .getPath()
259                                 : null;
260                         try {
261                             resolverResult.addDependency(node, node.getDependency(), filter, path);
262                         } catch (IOException e) {
263                             throw cannotReadModuleInfo(path, e);
264                         }
265                     }
266                 }
267                 result = resolverResult;
268             }
269         } finally {
270             RequestTraceHelper.exit(trace);
271         }
272         return result;
273     }
274 
275     private static DependencyResolverException cannotReadModuleInfo(final Path path, final IOException cause) {
276         return new DependencyResolverException("Cannot read module information of " + path, cause);
277     }
278 
279     private static boolean containsUnresolvedExpression(String value) {
280         return value != null && value.contains("${") && value.contains("}");
281     }
282 
283     private static String enhanceCollectionError(DependencyCollectionException e, CollectRequest request) {
284         if (e.getMessage() != null && e.getMessage().contains("Invalid Collect Request")) {
285             StringBuilder enhanced = new StringBuilder();
286             enhanced.append("Failed to collect dependencies");
287 
288             org.eclipse.aether.graph.Dependency root = request.getRoot();
289             if (root != null && root.getArtifact() != null) {
290                 org.eclipse.aether.artifact.Artifact artifact = root.getArtifact();
291                 String groupId = artifact.getGroupId();
292                 String artifactId = artifact.getArtifactId();
293                 String version = artifact.getVersion();
294 
295                 if (containsUnresolvedExpression(groupId)
296                         || containsUnresolvedExpression(artifactId)
297                         || containsUnresolvedExpression(version)) {
298                     enhanced.append(" due to unresolved expression(s) in dependency: ")
299                             .append(groupId)
300                             .append(":")
301                             .append(artifactId)
302                             .append(":")
303                             .append(version)
304                             .append(".\n")
305                             .append("Please check that all properties are defined in your POM or settings.xml.");
306                     return enhanced.toString();
307                 }
308             }
309 
310             for (org.eclipse.aether.graph.Dependency dep : request.getDependencies()) {
311                 if (dep != null && dep.getArtifact() != null) {
312                     org.eclipse.aether.artifact.Artifact artifact = dep.getArtifact();
313                     String groupId = artifact.getGroupId();
314                     String artifactId = artifact.getArtifactId();
315                     String version = artifact.getVersion();
316 
317                     if (containsUnresolvedExpression(groupId)
318                             || containsUnresolvedExpression(artifactId)
319                             || containsUnresolvedExpression(version)) {
320                         enhanced.append(" due to unresolved expression(s) in dependency: ")
321                                 .append(groupId)
322                                 .append(":")
323                                 .append(artifactId)
324                                 .append(":")
325                                 .append(version)
326                                 .append(".\n")
327                                 .append("Please check that all properties are defined in your POM or settings.xml.");
328                         return enhanced.toString();
329                     }
330                 }
331             }
332         }
333         return e.getMessage();
334     }
335 }