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