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