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