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.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Set;
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 HashMap<>();
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                 throw new DependencyResolverException("Unable to collect dependencies", e);
178             }
179         } finally {
180             RequestTraceHelper.exit(trace);
181         }
182     }
183 
184     @Nonnull
185     @Override
186     public List<Node> flatten(@Nonnull Session s, @Nonnull Node node, @Nullable PathScope scope)
187             throws DependencyResolverException {
188         InternalSession session = InternalSession.from(s);
189         DependencyNode root = cast(AbstractNode.class, node, "node").getDependencyNode();
190         List<DependencyNode> dependencies = session.getRepositorySystem()
191                 .flattenDependencyNodes(session.getSession(), root, getScopeDependencyFilter(scope));
192         dependencies.remove(root);
193         return map(dependencies, session::getNode);
194     }
195 
196     private static DependencyFilter getScopeDependencyFilter(PathScope scope) {
197         if (scope == null) {
198             return null;
199         }
200         Set<String> scopes =
201                 scope.dependencyScopes().stream().map(DependencyScope::id).collect(Collectors.toSet());
202         return (n, p) -> {
203             org.eclipse.aether.graph.Dependency d = n.getDependency();
204             return d == null || scopes.contains(d.getScope());
205         };
206     }
207 
208     /**
209      * Collects, flattens and resolves the dependencies.
210      *
211      * @param request the request to resolve
212      * @return the result of the resolution
213      */
214     @Override
215     public DependencyResolverResult resolve(DependencyResolverRequest request)
216             throws DependencyResolverException, ArtifactResolverException {
217         InternalSession session =
218                 InternalSession.from(requireNonNull(request, "request").getSession());
219         RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(session, request);
220         DependencyResolverResult result;
221         try {
222             DependencyResolverResult collectorResult = collect(request);
223             List<RemoteRepository> repositories = request.getRepositories() != null
224                     ? request.getRepositories()
225                     : request.getProject().isPresent()
226                             ? session.getService(ProjectManager.class)
227                                     .getRemoteProjectRepositories(
228                                             request.getProject().get())
229                             : session.getRemoteRepositories();
230             if (request.getRequestType() == DependencyResolverRequest.RequestType.COLLECT) {
231                 result = collectorResult;
232             } else {
233                 List<Node> nodes = flatten(session, collectorResult.getRoot(), request.getPathScope());
234                 List<ArtifactCoordinates> coordinates = nodes.stream()
235                         .map(Node::getDependency)
236                         .filter(Objects::nonNull)
237                         .map(Artifact::toCoordinates)
238                         .collect(Collectors.toList());
239                 Predicate<PathType> filter = request.getPathTypeFilter();
240                 DefaultDependencyResolverResult resolverResult = new DefaultDependencyResolverResult(
241                         null,
242                         moduleCache(request),
243                         collectorResult.getExceptions(),
244                         collectorResult.getRoot(),
245                         nodes.size());
246                 if (request.getRequestType() == DependencyResolverRequest.RequestType.FLATTEN) {
247                     for (Node node : nodes) {
248                         resolverResult.addNode(node);
249                     }
250                 } else {
251                     ArtifactResolverResult artifactResolverResult =
252                             session.getService(ArtifactResolver.class).resolve(session, coordinates, repositories);
253                     for (Node node : nodes) {
254                         Path path = (node.getArtifact() != null)
255                                 ? artifactResolverResult
256                                         .getResult(node.getArtifact().toCoordinates())
257                                         .getPath()
258                                 : null;
259                         try {
260                             resolverResult.addDependency(node, node.getDependency(), filter, path);
261                         } catch (IOException e) {
262                             throw cannotReadModuleInfo(path, e);
263                         }
264                     }
265                 }
266                 result = resolverResult;
267             }
268         } finally {
269             RequestTraceHelper.exit(trace);
270         }
271         return result;
272     }
273 
274     private static DependencyResolverException cannotReadModuleInfo(final Path path, final IOException cause) {
275         return new DependencyResolverException("Cannot read module information of " + path, cause);
276     }
277 }