View Javadoc
1   package org.apache.maven.plugin.surefire;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import javax.annotation.Nonnull;
23  import javax.annotation.Nullable;
24  
25  import java.util.Collection;
26  import java.util.Iterator;
27  import java.util.LinkedHashMap;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.artifact.repository.ArtifactRepository;
35  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
36  import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
37  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
38  import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
39  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
40  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
41  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
42  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
43  import org.apache.maven.artifact.versioning.VersionRange;
44  import org.apache.maven.model.Dependency;
45  import org.apache.maven.model.Plugin;
46  import org.apache.maven.plugin.MojoExecutionException;
47  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
48  import org.apache.maven.repository.RepositorySystem;
49  
50  import static java.util.Arrays.asList;
51  import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE;
52  import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE_PLUS_RUNTIME;
53  import static org.apache.maven.artifact.Artifact.SCOPE_RUNTIME;
54  import static org.apache.maven.artifact.ArtifactUtils.artifactMapByVersionlessId;
55  import static org.apache.maven.artifact.versioning.VersionRange.createFromVersionSpec;
56  
57  /**
58   * Does dependency resolution and artifact handling for the surefire plugin.
59   *
60   * @author Stephen Connolly
61   * @author Kristian Rosenvold
62   */
63  final class SurefireDependencyResolver
64  {
65      static final String PROVIDER_GROUP_ID = "org.apache.maven.surefire";
66  
67      private static final String[] PROVIDER_CLASSPATH_ORDER = {
68          "surefire-junit3",
69          "surefire-junit4",
70          "surefire-junit47",
71          "surefire-testng",
72          "surefire-junit-platform",
73          "surefire-api",
74          "surefire-logger-api",
75          "surefire-shared-utils",
76          "common-java5",
77          "common-junit3",
78          "common-junit4",
79          "common-junit48",
80          "common-testng-utils"
81      };
82  
83      private final RepositorySystem repositorySystem;
84  
85      private final ConsoleLogger log;
86  
87      private final ArtifactRepository localRepository;
88  
89      private final List<ArtifactRepository> pluginRemoteRepositories;
90  
91      private final List<ArtifactRepository> projectRemoteRepositories;
92  
93      private final ResolutionErrorHandler resolutionErrorHandler;
94  
95      private final String pluginName;
96  
97      private final boolean offline;
98  
99      SurefireDependencyResolver( RepositorySystem repositorySystem, ConsoleLogger log,
100                                 ArtifactRepository localRepository,
101                                 List<ArtifactRepository> pluginRemoteRepositories,
102                                 List<ArtifactRepository> projectRemoteRepositories,
103                                 ResolutionErrorHandler resolutionErrorHandler,
104                                 String pluginName, boolean offline )
105     {
106         this.repositorySystem = repositorySystem;
107         this.log = log;
108         this.localRepository = localRepository;
109         this.pluginRemoteRepositories = pluginRemoteRepositories;
110         this.projectRemoteRepositories = projectRemoteRepositories;
111         this.resolutionErrorHandler = resolutionErrorHandler;
112         this.pluginName = pluginName;
113         this.offline = offline;
114     }
115 
116     static boolean isWithinVersionSpec( @Nullable Artifact artifact, @Nonnull String versionSpec )
117     {
118         if ( artifact == null )
119         {
120             return false;
121         }
122         try
123         {
124             VersionRange range = createFromVersionSpec( versionSpec );
125             try
126             {
127                 return range.containsVersion( artifact.getSelectedVersion() );
128             }
129             catch ( NullPointerException e )
130             {
131                 return range.containsVersion( new DefaultArtifactVersion( artifact.getBaseVersion() ) );
132             }
133         }
134         catch ( InvalidVersionSpecificationException | OverConstrainedVersionException e )
135         {
136             throw new RuntimeException( "Bug in plugin. Please report with stacktrace" );
137         }
138     }
139 
140     Map<String, Artifact> resolvePluginDependencies( Plugin plugin, Map<String, Artifact> pluginResolvedDependencies )
141         throws MojoExecutionException
142     {
143         Map<String, Artifact> resolved = new LinkedHashMap<>();
144         Collection<Dependency> pluginDependencies = plugin.getDependencies();
145 
146         for ( Dependency dependency : pluginDependencies )
147         {
148             Artifact dependencyArtifact = repositorySystem.createDependencyArtifact( dependency );
149             ArtifactResolutionResult artifactResolutionResult = resolvePluginArtifact( dependencyArtifact );
150             for ( Artifact artifact : artifactResolutionResult.getArtifacts() )
151             {
152                 String key = artifact.getGroupId() + ":" + artifact.getArtifactId();
153                 Artifact resolvedPluginDependency = pluginResolvedDependencies.get( key );
154                 if ( resolvedPluginDependency != null )
155                 {
156                     resolved.put( key, artifact );
157                 }
158             }
159         }
160         return resolved;
161     }
162 
163     ArtifactResolutionResult resolvePluginArtifact( Artifact artifact ) throws MojoExecutionException
164     {
165         return resolvePluginArtifact( artifact, new RuntimeArtifactFilter() );
166     }
167 
168     ArtifactResolutionResult resolveProjectArtifact( Artifact artifact ) throws MojoExecutionException
169     {
170         return resolveProjectArtifact( artifact, new RuntimeArtifactFilter() );
171     }
172 
173     private ArtifactResolutionResult resolvePluginArtifact( Artifact artifact, ArtifactFilter filter )
174         throws MojoExecutionException
175     {
176         return resolveArtifact( artifact, pluginRemoteRepositories, filter );
177     }
178 
179     private ArtifactResolutionResult resolveProjectArtifact( Artifact artifact, ArtifactFilter filter )
180         throws MojoExecutionException
181     {
182         return resolveArtifact( artifact, projectRemoteRepositories, filter );
183     }
184 
185     private ArtifactResolutionResult resolveArtifact( Artifact artifact, List<ArtifactRepository> repositories,
186                                                       ArtifactFilter filter ) throws MojoExecutionException
187     {
188         ArtifactResolutionRequest request = new ArtifactResolutionRequest()
189             .setOffline( offline )
190             .setArtifact( artifact )
191             .setLocalRepository( localRepository )
192             .setResolveTransitively( true )
193             .setCollectionFilter( filter )
194             .setRemoteRepositories( repositories );
195 
196         ArtifactResolutionResult result = repositorySystem.resolve( request );
197         try
198         {
199             resolutionErrorHandler.throwErrors( request, result );
200         }
201         catch ( ArtifactResolutionException e )
202         {
203             throw new MojoExecutionException( e.getMessage(), e );
204         }
205 
206         return result;
207     }
208 
209     @Nonnull
210     Set<Artifact> getProviderClasspath( String providerArtifactId, String providerVersion )
211         throws MojoExecutionException
212     {
213         Dependency provider = toProviderDependency( providerArtifactId, providerVersion );
214 
215         Artifact providerArtifact = repositorySystem.createDependencyArtifact( provider );
216 
217         ArtifactResolutionResult result = resolvePluginArtifact( providerArtifact );
218 
219         if ( log.isDebugEnabled() )
220         {
221             for ( Artifact artifact : result.getArtifacts() )
222             {
223                 String artifactPath = artifact.getFile().getAbsolutePath();
224                 String scope = artifact.getScope();
225                 log.debug( "Adding to " + pluginName + " test classpath: " + artifactPath + " Scope: " + scope );
226             }
227         }
228 
229         return orderProviderArtifacts( result.getArtifacts() );
230     }
231 
232     @Nonnull
233     Map<String, Artifact> getProviderClasspathAsMap( String providerArtifactId, String providerVersion )
234         throws MojoExecutionException
235     {
236         return artifactMapByVersionlessId( getProviderClasspath( providerArtifactId, providerVersion ) );
237     }
238 
239     // FIXME
240     // method argument should be unchanged
241     // what if providerArtifacts will be unmodifiable
242     private static Set<Artifact> orderProviderArtifacts( Set<Artifact> providerArtifacts )
243     {
244         Set<Artifact> orderedProviderArtifacts = new LinkedHashSet<>();
245         for ( String order : PROVIDER_CLASSPATH_ORDER )
246         {
247             Iterator<Artifact> providerArtifactsIt = providerArtifacts.iterator();
248             while ( providerArtifactsIt.hasNext() )
249             {
250                 Artifact providerArtifact = providerArtifactsIt.next();
251                 if ( providerArtifact.getArtifactId().equals( order ) )
252                 {
253                     orderedProviderArtifacts.add( providerArtifact );
254                     providerArtifactsIt.remove();
255                 }
256             }
257         }
258         orderedProviderArtifacts.addAll( providerArtifacts );
259         return orderedProviderArtifacts;
260     }
261 
262     private static Dependency toProviderDependency( String providerArtifactId, String providerVersion )
263     {
264         Dependency dependency = new Dependency();
265         dependency.setGroupId( PROVIDER_GROUP_ID );
266         dependency.setArtifactId( providerArtifactId );
267         dependency.setVersion( providerVersion );
268         dependency.setType( "jar" );
269         return dependency;
270     }
271 
272     static class RuntimeArtifactFilter implements ArtifactFilter
273     {
274         private static final Collection<String> SCOPES =
275             asList( SCOPE_COMPILE, SCOPE_COMPILE_PLUS_RUNTIME, SCOPE_RUNTIME );
276 
277         private final Artifact filter;
278 
279         RuntimeArtifactFilter()
280         {
281             this( null );
282         }
283 
284         RuntimeArtifactFilter( Artifact filter )
285         {
286             this.filter = filter;
287         }
288 
289         @Override
290         public boolean include( Artifact artifact )
291         {
292             String scope = artifact.getScope();
293             return ( filter == null || artifact.equals( filter ) )
294                 && !artifact.isOptional() && ( scope == null || SCOPES.contains( scope ) );
295         }
296     }
297 }