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.plugin.internal;
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.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Objects;
30  import java.util.stream.Collectors;
31  import java.util.stream.Stream;
32  
33  import org.apache.maven.RepositoryUtils;
34  import org.apache.maven.model.Dependency;
35  import org.apache.maven.model.Plugin;
36  import org.apache.maven.plugin.PluginResolutionException;
37  import org.codehaus.plexus.util.StringUtils;
38  import org.eclipse.aether.DefaultRepositorySystemSession;
39  import org.eclipse.aether.RepositorySystem;
40  import org.eclipse.aether.RepositorySystemSession;
41  import org.eclipse.aether.RequestTrace;
42  import org.eclipse.aether.artifact.Artifact;
43  import org.eclipse.aether.artifact.DefaultArtifact;
44  import org.eclipse.aether.collection.CollectRequest;
45  import org.eclipse.aether.collection.DependencyCollectionChecker;
46  import org.eclipse.aether.collection.DependencyCollectionException;
47  import org.eclipse.aether.collection.DependencySelector;
48  import org.eclipse.aether.collection.VersionFilterBuilder;
49  import org.eclipse.aether.graph.DependencyFilter;
50  import org.eclipse.aether.graph.DependencyNode;
51  import org.eclipse.aether.graph.DependencyVisitor;
52  import org.eclipse.aether.repository.RemoteRepository;
53  import org.eclipse.aether.resolution.ArtifactDescriptorException;
54  import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
55  import org.eclipse.aether.resolution.ArtifactDescriptorResult;
56  import org.eclipse.aether.resolution.ArtifactRequest;
57  import org.eclipse.aether.resolution.ArtifactResolutionException;
58  import org.eclipse.aether.resolution.DependencyRequest;
59  import org.eclipse.aether.resolution.DependencyResolutionException;
60  import org.eclipse.aether.resolution.DependencyResult;
61  import org.eclipse.aether.util.artifact.JavaScopes;
62  import org.eclipse.aether.util.filter.AndDependencyFilter;
63  import org.eclipse.aether.util.filter.ScopeDependencyFilter;
64  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
65  import org.eclipse.aether.util.graph.selector.AndDependencySelector;
66  import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
67  import org.slf4j.Logger;
68  import org.slf4j.LoggerFactory;
69  
70  /**
71   * Assists in resolving the dependencies of a plugin. <strong>Warning:</strong> This is an internal utility class that
72   * is only public for technical reasons, it is not part of the public API. In particular, this class can be changed or
73   * deleted without prior notice.
74   *
75   * @since 3.0
76   * @author Benjamin Bentmann
77   */
78  @Singleton
79  @Named
80  public class DefaultPluginDependenciesResolver implements PluginDependenciesResolver {
81  
82      private static final String REPOSITORY_CONTEXT = "plugin";
83  
84      private final Logger logger = LoggerFactory.getLogger(getClass());
85  
86      @Inject
87      private RepositorySystem repoSystem;
88  
89      @Inject
90      private List<MavenPluginDependenciesValidator> dependenciesValidators;
91  
92      private Artifact toArtifact(Plugin plugin, RepositorySystemSession session) {
93          return new DefaultArtifact(
94                  plugin.getGroupId(),
95                  plugin.getArtifactId(),
96                  null,
97                  "jar",
98                  plugin.getVersion(),
99                  session.getArtifactTypeRegistry().get("maven-plugin"));
100     }
101 
102     @Override
103     public Artifact resolve(Plugin plugin, List<RemoteRepository> repositories, RepositorySystemSession session)
104             throws PluginResolutionException {
105         RequestTrace trace = RequestTrace.newChild(null, plugin);
106 
107         Artifact pluginArtifact = toArtifact(plugin, session);
108 
109         try {
110             DefaultRepositorySystemSession pluginSession = new DefaultRepositorySystemSession(session);
111             pluginSession.setArtifactDescriptorPolicy(new SimpleArtifactDescriptorPolicy(true, false));
112 
113             ArtifactDescriptorRequest request =
114                     new ArtifactDescriptorRequest(pluginArtifact, repositories, REPOSITORY_CONTEXT);
115             request.setTrace(trace);
116             ArtifactDescriptorResult result = repoSystem.readArtifactDescriptor(pluginSession, request);
117 
118             for (MavenPluginDependenciesValidator dependenciesValidator : dependenciesValidators) {
119                 dependenciesValidator.validate(session, pluginArtifact, result);
120             }
121 
122             pluginArtifact = result.getArtifact();
123 
124             if (logger.isWarnEnabled()) {
125                 if (!result.getRelocations().isEmpty()) {
126                     String message = pluginArtifact instanceof org.apache.maven.repository.internal.RelocatedArtifact
127                             ? ((org.apache.maven.repository.internal.RelocatedArtifact) pluginArtifact).getMessage()
128                             : null;
129                     logger.warn("The artifact " + result.getRelocations().get(0) + " has been relocated to "
130                             + pluginArtifact + (message != null ? ": " + message : ""));
131                 }
132             }
133 
134             String requiredMavenVersion = (String) result.getProperties().get("prerequisites.maven");
135             if (requiredMavenVersion != null) {
136                 Map<String, String> props = new LinkedHashMap<>(pluginArtifact.getProperties());
137                 props.put("requiredMavenVersion", requiredMavenVersion);
138                 pluginArtifact = pluginArtifact.setProperties(props);
139             }
140         } catch (ArtifactDescriptorException e) {
141             throw new PluginResolutionException(
142                     plugin, e.getResult().getExceptions(), logger.isDebugEnabled() ? e : null);
143         }
144 
145         try {
146             ArtifactRequest request = new ArtifactRequest(pluginArtifact, repositories, REPOSITORY_CONTEXT);
147             request.setTrace(trace);
148             pluginArtifact = repoSystem.resolveArtifact(session, request).getArtifact();
149         } catch (ArtifactResolutionException e) {
150             throw new PluginResolutionException(
151                     plugin, e.getResult().getExceptions(), logger.isDebugEnabled() ? e : null);
152         }
153 
154         return pluginArtifact;
155     }
156 
157     /**
158      * @since 3.3.0
159      * @deprecated Is unused since 3.10.0
160      */
161     @Deprecated
162     public DependencyNode resolveCoreExtension(
163             Plugin plugin,
164             DependencyFilter dependencyFilter,
165             List<RemoteRepository> repositories,
166             RepositorySystemSession session)
167             throws PluginResolutionException {
168         return resolveInternal(plugin, null /* pluginArtifact */, dependencyFilter, repositories, session)
169                 .getRoot();
170     }
171 
172     /**
173      * @since 3.10.0
174      */
175     @Override
176     public DependencyResult resolveCoreExtensionAndFlatten(
177             Plugin plugin,
178             DependencyFilter dependencyFilter,
179             List<RemoteRepository> repositories,
180             RepositorySystemSession session)
181             throws PluginResolutionException {
182         return resolveInternal(plugin, null /* pluginArtifact */, dependencyFilter, repositories, session);
183     }
184 
185     @Override
186     public DependencyNode resolve(
187             Plugin plugin,
188             Artifact pluginArtifact,
189             DependencyFilter dependencyFilter,
190             List<RemoteRepository> repositories,
191             RepositorySystemSession session)
192             throws PluginResolutionException {
193         return resolveInternal(plugin, pluginArtifact, dependencyFilter, repositories, session)
194                 .getRoot();
195     }
196 
197     @Override
198     public DependencyResult resolvePluginAndFlatten(
199             Plugin plugin,
200             Artifact pluginArtifact,
201             DependencyFilter dependencyFilter,
202             List<RemoteRepository> repositories,
203             RepositorySystemSession session)
204             throws PluginResolutionException {
205         return resolveInternal(plugin, pluginArtifact, dependencyFilter, repositories, session);
206     }
207 
208     private DependencyResult resolveInternal(
209             Plugin plugin,
210             Artifact pluginArtifact,
211             DependencyFilter dependencyFilter,
212             List<RemoteRepository> repositories,
213             RepositorySystemSession session)
214             throws PluginResolutionException {
215         RequestTrace trace = RequestTrace.newChild(null, plugin);
216 
217         if (pluginArtifact == null) {
218             pluginArtifact = toArtifact(plugin, session);
219         }
220 
221         DependencyFilter collectionFilter = new ScopeDependencyFilter("provided", "test");
222         DependencyFilter resolutionFilter = AndDependencyFilter.newInstance(collectionFilter, dependencyFilter);
223 
224         try {
225             DependencySelector selector =
226                     AndDependencySelector.newInstance(session.getDependencySelector(), new WagonExcluder());
227 
228             DefaultRepositorySystemSession pluginSession = new DefaultRepositorySystemSession(session);
229             pluginSession.setConfigProperty(VersionFilterBuilder.VERSION_FILTER_SUPPRESSED, Boolean.TRUE.toString());
230             pluginSession.setConfigProperty(
231                     DependencyCollectionChecker.COLLECTOR_CHECKER_SUPPRESSED, Boolean.TRUE.toString());
232             pluginSession.setDependencySelector(selector);
233             pluginSession.setDependencyGraphTransformer(session.getDependencyGraphTransformer());
234 
235             CollectRequest request = new CollectRequest();
236             request.setRequestContext(REPOSITORY_CONTEXT);
237             request.setRepositories(repositories);
238             request.setRoot(new org.eclipse.aether.graph.Dependency(pluginArtifact, null));
239             for (Dependency dependency : plugin.getDependencies()) {
240                 org.eclipse.aether.graph.Dependency pluginDep =
241                         RepositoryUtils.toDependency(dependency, session.getArtifactTypeRegistry());
242                 if (!JavaScopes.SYSTEM.equals(pluginDep.getScope())) {
243                     pluginDep = pluginDep.setScope(JavaScopes.RUNTIME);
244                 }
245                 request.addDependency(pluginDep);
246             }
247 
248             DependencyRequest depRequest = new DependencyRequest(request, resolutionFilter);
249             depRequest.setTrace(trace);
250 
251             request.setTrace(RequestTrace.newChild(trace, depRequest));
252 
253             DependencyNode node =
254                     repoSystem.collectDependencies(pluginSession, request).getRoot();
255 
256             if (logger.isDebugEnabled()) {
257                 node.accept(new GraphLogger());
258             }
259 
260             depRequest.setRoot(node);
261             return repoSystem.resolveDependencies(session, depRequest);
262 
263         } catch (DependencyCollectionException e) {
264             throw new PluginResolutionException(
265                     plugin, e.getResult().getExceptions(), logger.isDebugEnabled() ? e : null);
266         } catch (DependencyResolutionException e) {
267             List<Exception> exceptions = Stream.concat(
268                             e.getResult().getCollectExceptions().stream(),
269                             e.getResult().getArtifactResults().stream()
270                                     .filter(r -> !r.isResolved())
271                                     .flatMap(r -> r.getExceptions().stream()))
272                     .collect(Collectors.toList());
273             throw new PluginResolutionException(plugin, exceptions, logger.isDebugEnabled() ? e : null);
274         }
275     }
276 
277     // Keep this class in sync with org.apache.maven.project.DefaultProjectDependenciesResolver.GraphLogger
278     class GraphLogger implements DependencyVisitor {
279 
280         private String indent = "";
281 
282         public boolean visitEnter(DependencyNode node) {
283             StringBuilder buffer = new StringBuilder(128);
284             buffer.append(indent);
285             org.eclipse.aether.graph.Dependency dep = node.getDependency();
286             if (dep != null) {
287                 org.eclipse.aether.artifact.Artifact art = dep.getArtifact();
288 
289                 buffer.append(art);
290                 if (StringUtils.isNotEmpty(dep.getScope())) {
291                     buffer.append(':').append(dep.getScope());
292                 }
293 
294                 if (dep.isOptional()) {
295                     buffer.append(" (optional)");
296                 }
297 
298                 // TODO We currently cannot tell which <dependencyManagement> section contained the management
299                 //      information. When the resolver provides this information, these log messages should be updated
300                 //      to contain it.
301                 if ((node.getManagedBits() & DependencyNode.MANAGED_SCOPE) == DependencyNode.MANAGED_SCOPE) {
302                     final String premanagedScope = DependencyManagerUtils.getPremanagedScope(node);
303                     buffer.append(" (scope managed from ");
304                     buffer.append(Objects.toString(premanagedScope, "default"));
305                     buffer.append(')');
306                 }
307 
308                 if ((node.getManagedBits() & DependencyNode.MANAGED_VERSION) == DependencyNode.MANAGED_VERSION) {
309                     final String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(node);
310                     buffer.append(" (version managed from ");
311                     buffer.append(Objects.toString(premanagedVersion, "default"));
312                     buffer.append(')');
313                 }
314 
315                 if ((node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == DependencyNode.MANAGED_OPTIONAL) {
316                     final Boolean premanagedOptional = DependencyManagerUtils.getPremanagedOptional(node);
317                     buffer.append(" (optionality managed from ");
318                     buffer.append(Objects.toString(premanagedOptional, "default"));
319                     buffer.append(')');
320                 }
321 
322                 if ((node.getManagedBits() & DependencyNode.MANAGED_EXCLUSIONS) == DependencyNode.MANAGED_EXCLUSIONS) {
323                     final Collection<org.eclipse.aether.graph.Exclusion> premanagedExclusions =
324                             DependencyManagerUtils.getPremanagedExclusions(node);
325 
326                     buffer.append(" (exclusions managed from ");
327                     buffer.append(Objects.toString(premanagedExclusions, "default"));
328                     buffer.append(')');
329                 }
330 
331                 if ((node.getManagedBits() & DependencyNode.MANAGED_PROPERTIES) == DependencyNode.MANAGED_PROPERTIES) {
332                     final Map<String, String> premanagedProperties =
333                             DependencyManagerUtils.getPremanagedProperties(node);
334 
335                     buffer.append(" (properties managed from ");
336                     buffer.append(Objects.toString(premanagedProperties, "default"));
337                     buffer.append(')');
338                 }
339             }
340 
341             logger.debug(buffer.toString());
342             indent += "   ";
343             return true;
344         }
345 
346         public boolean visitLeave(DependencyNode node) {
347             indent = indent.substring(0, indent.length() - 3);
348             return true;
349         }
350     }
351 }