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.surefire;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  import javax.inject.Inject;
24  import javax.inject.Named;
25  import javax.inject.Singleton;
26  
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Iterator;
30  import java.util.LinkedHashMap;
31  import java.util.LinkedHashSet;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.stream.Collectors;
36  
37  import org.apache.maven.RepositoryUtils;
38  import org.apache.maven.artifact.Artifact;
39  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
40  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
41  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
42  import org.apache.maven.artifact.versioning.VersionRange;
43  import org.apache.maven.model.Dependency;
44  import org.apache.maven.model.Plugin;
45  import org.apache.maven.plugin.MojoExecutionException;
46  import org.eclipse.aether.RepositorySystem;
47  import org.eclipse.aether.RepositorySystemSession;
48  import org.eclipse.aether.collection.CollectRequest;
49  import org.eclipse.aether.graph.DependencyFilter;
50  import org.eclipse.aether.repository.RemoteRepository;
51  import org.eclipse.aether.resolution.ArtifactResult;
52  import org.eclipse.aether.resolution.DependencyRequest;
53  import org.eclipse.aether.resolution.DependencyResolutionException;
54  import org.eclipse.aether.resolution.DependencyResult;
55  import org.eclipse.aether.util.artifact.JavaScopes;
56  import org.eclipse.aether.util.filter.DependencyFilterUtils;
57  
58  import static org.apache.maven.artifact.ArtifactUtils.artifactMapByVersionlessId;
59  import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
60  
61  /**
62   * Internal component used by the surefire plugin. Not a public API.
63   * not guaranteed to remain unchanged across surefire plugin versions.
64   * Does dependency resolution and artifact handling for the surefire plugin.
65   *
66   * @author Stephen Connolly
67   * @author Kristian Rosenvold
68   */
69  @Named
70  @Singleton
71  public class SurefireDependencyResolver {
72  
73      static final String PROVIDER_GROUP_ID = "org.apache.maven.surefire";
74  
75      private static final String[] PROVIDER_CLASSPATH_ORDER = {
76          "surefire-junit-platform", "surefire-api", "surefire-logger-api", "surefire-shared-utils", "common-java5"
77      };
78  
79      private final RepositorySystem repositorySystem;
80  
81      @Inject
82      SurefireDependencyResolver(RepositorySystem repositorySystem) {
83          this.repositorySystem = repositorySystem;
84      }
85  
86      public static boolean isWithinVersionSpec(@Nullable Artifact artifact, @Nonnull String versionSpec) {
87          if (artifact == null) {
88              return false;
89          }
90          try {
91              VersionRange range = createFromVersionSpec(versionSpec);
92              try {
93                  return range.containsVersion(artifact.getSelectedVersion());
94              } catch (NullPointerException e) {
95                  return range.containsVersion(new DefaultArtifactVersion(artifact.getBaseVersion()));
96              }
97          } catch (InvalidVersionSpecificationException | OverConstrainedVersionException e) {
98              throw new RuntimeException("Bug in plugin. Please report with stacktrace");
99          }
100     }
101 
102     Map<String, Artifact> resolvePluginDependencies(
103             RepositorySystemSession session,
104             List<RemoteRepository> repositories,
105             Plugin plugin,
106             Map<String, Artifact> pluginResolvedDependencies)
107             throws MojoExecutionException {
108         Map<String, Artifact> resolved = new LinkedHashMap<>();
109         Collection<Dependency> pluginDependencies = plugin.getDependencies();
110 
111         for (Dependency dependency : pluginDependencies) {
112             Set<Artifact> artifacts = resolveDependencies(
113                     session, repositories, RepositoryUtils.toDependency(dependency, session.getArtifactTypeRegistry()));
114             for (Artifact artifact : artifacts) {
115                 String key = artifact.getGroupId() + ":" + artifact.getArtifactId();
116                 Artifact resolvedPluginDependency = pluginResolvedDependencies.get(key);
117                 if (resolvedPluginDependency != null) {
118                     resolved.put(key, artifact);
119                 }
120             }
121         }
122         return resolved;
123     }
124 
125     public Set<Artifact> resolveArtifacts(
126             RepositorySystemSession session, List<RemoteRepository> repositories, Artifact artifact)
127             throws MojoExecutionException {
128         return resolveDependencies(session, repositories, RepositoryUtils.toDependency(artifact, null));
129     }
130 
131     public Set<Artifact> resolveDependencies(
132             RepositorySystemSession session, List<RemoteRepository> repositories, Dependency dependency)
133             throws MojoExecutionException {
134         return resolveDependencies(
135                 session, repositories, RepositoryUtils.toDependency(dependency, session.getArtifactTypeRegistry()));
136     }
137 
138     private Set<Artifact> resolveDependencies(
139             RepositorySystemSession session,
140             List<RemoteRepository> repositories,
141             org.eclipse.aether.graph.Dependency dependency)
142             throws MojoExecutionException {
143 
144         List<ArtifactResult> results = resolveDependencies(
145                 session, repositories, dependency, DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME));
146         return results.stream()
147                 .map(ArtifactResult::getArtifact)
148                 .map(RepositoryUtils::toArtifact)
149                 .collect(Collectors.toCollection(LinkedHashSet::new));
150     }
151 
152     private List<ArtifactResult> resolveDependencies(
153             RepositorySystemSession session,
154             List<RemoteRepository> repositories,
155             org.eclipse.aether.graph.Dependency dependency,
156             DependencyFilter dependencyFilter)
157             throws MojoExecutionException {
158 
159         try {
160             // use a collect request without a root in order to not resolve optional dependencies
161             CollectRequest collectRequest =
162                     new CollectRequest(Collections.singletonList(dependency), null, repositories);
163 
164             DependencyRequest request = new DependencyRequest();
165             request.setCollectRequest(collectRequest);
166             request.setFilter(dependencyFilter);
167 
168             DependencyResult dependencyResult = repositorySystem.resolveDependencies(session, request);
169             return dependencyResult.getArtifactResults();
170         } catch (DependencyResolutionException e) {
171             throw new MojoExecutionException(e.getMessage(), e);
172         }
173     }
174 
175     @Nonnull
176     Set<Artifact> getProviderClasspath(
177             RepositorySystemSession session,
178             List<RemoteRepository> repositories,
179             String providerArtifactId,
180             String providerVersion)
181             throws MojoExecutionException {
182 
183         Dependency provider = toProviderDependency(providerArtifactId, providerVersion);
184 
185         org.eclipse.aether.graph.Dependency dependency =
186                 RepositoryUtils.toDependency(provider, session.getArtifactTypeRegistry());
187 
188         Set<Artifact> result = resolveDependencies(session, repositories, dependency);
189 
190         return orderProviderArtifacts(result);
191     }
192 
193     @Nonnull
194     Map<String, Artifact> getProviderClasspathAsMap(
195             RepositorySystemSession session,
196             List<RemoteRepository> repositories,
197             String providerArtifactId,
198             String providerVersion)
199             throws MojoExecutionException {
200         return artifactMapByVersionlessId(
201                 getProviderClasspath(session, repositories, providerArtifactId, providerVersion));
202     }
203 
204     // FIXME
205     // method argument should be unchanged
206     // what if providerArtifacts will be unmodifiable
207     private static Set<Artifact> orderProviderArtifacts(Set<Artifact> providerArtifacts) {
208         Set<Artifact> orderedProviderArtifacts = new LinkedHashSet<>();
209         for (String order : PROVIDER_CLASSPATH_ORDER) {
210             Iterator<Artifact> providerArtifactsIt = providerArtifacts.iterator();
211             while (providerArtifactsIt.hasNext()) {
212                 Artifact providerArtifact = providerArtifactsIt.next();
213                 if (providerArtifact.getArtifactId().equals(order)) {
214                     orderedProviderArtifacts.add(providerArtifact);
215                     providerArtifactsIt.remove();
216                 }
217             }
218         }
219         orderedProviderArtifacts.addAll(providerArtifacts);
220         return orderedProviderArtifacts;
221     }
222 
223     private static Dependency toProviderDependency(String providerArtifactId, String providerVersion) {
224         Dependency dependency = new Dependency();
225         dependency.setGroupId(PROVIDER_GROUP_ID);
226         dependency.setArtifactId(providerArtifactId);
227         dependency.setVersion(providerVersion);
228         dependency.setType("jar");
229         return dependency;
230     }
231 }