1 package org.apache.maven.plugin.version.internal;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.TreeSet;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32
33 import org.apache.maven.artifact.repository.metadata.Metadata;
34 import org.apache.maven.artifact.repository.metadata.Versioning;
35 import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
36 import org.apache.maven.model.Build;
37 import org.apache.maven.model.Plugin;
38 import org.apache.maven.plugin.MavenPluginManager;
39 import org.apache.maven.plugin.PluginResolutionException;
40 import org.apache.maven.plugin.descriptor.PluginDescriptor;
41 import org.apache.maven.plugin.version.PluginVersionRequest;
42 import org.apache.maven.plugin.version.PluginVersionResolutionException;
43 import org.apache.maven.plugin.version.PluginVersionResolver;
44 import org.apache.maven.plugin.version.PluginVersionResult;
45 import org.codehaus.plexus.component.annotations.Component;
46 import org.codehaus.plexus.component.annotations.Requirement;
47 import org.codehaus.plexus.logging.Logger;
48 import org.codehaus.plexus.util.StringUtils;
49 import org.eclipse.aether.RepositoryEvent.EventType;
50 import org.eclipse.aether.RepositoryEvent;
51 import org.eclipse.aether.RepositoryListener;
52 import org.eclipse.aether.RepositorySystem;
53 import org.eclipse.aether.RepositorySystemSession;
54 import org.eclipse.aether.RequestTrace;
55 import org.eclipse.aether.SessionData;
56 import org.eclipse.aether.metadata.DefaultMetadata;
57 import org.eclipse.aether.repository.ArtifactRepository;
58 import org.eclipse.aether.repository.RemoteRepository;
59 import org.eclipse.aether.resolution.MetadataRequest;
60 import org.eclipse.aether.resolution.MetadataResult;
61 import org.eclipse.aether.util.version.GenericVersionScheme;
62 import org.eclipse.aether.version.InvalidVersionSpecificationException;
63 import org.eclipse.aether.version.Version;
64 import org.eclipse.aether.version.VersionScheme;
65
66
67
68
69
70
71
72 @Component( role = PluginVersionResolver.class )
73 public class DefaultPluginVersionResolver
74 implements PluginVersionResolver
75 {
76
77 private static final String REPOSITORY_CONTEXT = "plugin";
78
79 private static final Object CACHE_KEY = new Object();
80
81 @Requirement
82 private Logger logger;
83
84 @Requirement
85 private RepositorySystem repositorySystem;
86
87 @Requirement
88 private MetadataReader metadataReader;
89
90 @Requirement
91 private MavenPluginManager pluginManager;
92
93 public PluginVersionResult resolve( PluginVersionRequest request )
94 throws PluginVersionResolutionException
95 {
96 PluginVersionResult result = resolveFromProject( request );
97
98 if ( result == null )
99 {
100 ConcurrentMap<Key, PluginVersionResult> cache = getCache( request.getRepositorySession().getData() );
101 Key key = getKey( request );
102 result = cache.get( key );
103
104 if ( result == null )
105 {
106 result = resolveFromRepository( request );
107
108 if ( logger.isDebugEnabled() )
109 {
110 logger.debug( "Resolved plugin version for " + request.getGroupId() + ":" + request.getArtifactId()
111 + " to " + result.getVersion() + " from repository " + result.getRepository() );
112 }
113
114 cache.putIfAbsent( key, result );
115 }
116 else if ( logger.isDebugEnabled() )
117 {
118 logger.debug( "Reusing cached resolved plugin version for " + request.getGroupId() + ":"
119 + request.getArtifactId() + " to " + result.getVersion() + " from POM " + request.getPom() );
120 }
121 }
122 else if ( logger.isDebugEnabled() )
123 {
124 logger.debug( "Resolved plugin version for " + request.getGroupId() + ":" + request.getArtifactId() + " to "
125 + result.getVersion() + " from POM " + request.getPom() );
126 }
127
128 return result;
129 }
130
131 private PluginVersionResult resolveFromRepository( PluginVersionRequest request )
132 throws PluginVersionResolutionException
133 {
134 RequestTrace trace = RequestTrace.newChild( null, request );
135
136 DefaultPluginVersionResult result = new DefaultPluginVersionResult();
137
138 org.eclipse.aether.metadata.Metadata metadata =
139 new DefaultMetadata( request.getGroupId(), request.getArtifactId(), "maven-metadata.xml",
140 DefaultMetadata.Nature.RELEASE_OR_SNAPSHOT );
141
142 List<MetadataRequest> requests = new ArrayList<>();
143
144 requests.add( new MetadataRequest( metadata, null, REPOSITORY_CONTEXT ).setTrace( trace ) );
145
146 for ( RemoteRepository repository : request.getRepositories() )
147 {
148 requests.add( new MetadataRequest( metadata, repository, REPOSITORY_CONTEXT ).setTrace( trace ) );
149 }
150
151 List<MetadataResult> results = repositorySystem.resolveMetadata( request.getRepositorySession(), requests );
152
153 Versions versions = new Versions();
154
155 for ( MetadataResult res : results )
156 {
157 ArtifactRepository repository = res.getRequest().getRepository();
158 if ( repository == null )
159 {
160 repository = request.getRepositorySession().getLocalRepository();
161 }
162
163 mergeMetadata( request.getRepositorySession(), trace, versions, res.getMetadata(), repository );
164 }
165
166 selectVersion( result, request, versions );
167
168 return result;
169 }
170
171 private void selectVersion( DefaultPluginVersionResult result, PluginVersionRequest request, Versions versions )
172 throws PluginVersionResolutionException
173 {
174 String version = null;
175 ArtifactRepository repo = null;
176
177 if ( StringUtils.isNotEmpty( versions.releaseVersion ) )
178 {
179 version = versions.releaseVersion;
180 repo = versions.releaseRepository;
181 }
182 else if ( StringUtils.isNotEmpty( versions.latestVersion ) )
183 {
184 version = versions.latestVersion;
185 repo = versions.latestRepository;
186 }
187 if ( version != null && !isCompatible( request, version ) )
188 {
189 versions.versions.remove( version );
190 version = null;
191 }
192
193 if ( version == null )
194 {
195 VersionScheme versionScheme = new GenericVersionScheme();
196
197 TreeSet<Version> releases = new TreeSet<>( Collections.reverseOrder() );
198 TreeSet<Version> snapshots = new TreeSet<>( Collections.reverseOrder() );
199
200 for ( String ver : versions.versions.keySet() )
201 {
202 try
203 {
204 Version v = versionScheme.parseVersion( ver );
205
206 if ( ver.endsWith( "-SNAPSHOT" ) )
207 {
208 snapshots.add( v );
209 }
210 else
211 {
212 releases.add( v );
213 }
214 }
215 catch ( InvalidVersionSpecificationException e )
216 {
217
218 }
219 }
220
221 for ( Version v : releases )
222 {
223 String ver = v.toString();
224 if ( isCompatible( request, ver ) )
225 {
226 version = ver;
227 repo = versions.versions.get( version );
228 break;
229 }
230 }
231
232 if ( version == null )
233 {
234 for ( Version v : snapshots )
235 {
236 String ver = v.toString();
237 if ( isCompatible( request, ver ) )
238 {
239 version = ver;
240 repo = versions.versions.get( version );
241 break;
242 }
243 }
244 }
245 }
246
247 if ( version != null )
248 {
249 result.setVersion( version );
250 result.setRepository( repo );
251 }
252 else
253 {
254 throw new PluginVersionResolutionException( request.getGroupId(), request.getArtifactId(),
255 request.getRepositorySession().getLocalRepository(),
256 request.getRepositories(),
257 "Plugin not found in any plugin repository" );
258 }
259 }
260
261 private boolean isCompatible( PluginVersionRequest request, String version )
262 {
263 Plugin plugin = new Plugin();
264 plugin.setGroupId( request.getGroupId() );
265 plugin.setArtifactId( request.getArtifactId() );
266 plugin.setVersion( version );
267
268 PluginDescriptor pluginDescriptor;
269
270 try
271 {
272 pluginDescriptor =
273 pluginManager.getPluginDescriptor( plugin, request.getRepositories(), request.getRepositorySession() );
274 }
275 catch ( PluginResolutionException e )
276 {
277 logger.debug( "Ignoring unresolvable plugin version " + version, e );
278 return false;
279 }
280 catch ( Exception e )
281 {
282
283 return true;
284 }
285
286 try
287 {
288 pluginManager.checkRequiredMavenVersion( pluginDescriptor );
289 }
290 catch ( Exception e )
291 {
292 logger.debug( "Ignoring incompatible plugin version " + version + ": " + e.getMessage() );
293 return false;
294 }
295
296 return true;
297 }
298
299 private void mergeMetadata( RepositorySystemSession session, RequestTrace trace, Versions versions,
300 org.eclipse.aether.metadata.Metadata metadata, ArtifactRepository repository )
301 {
302 if ( metadata != null && metadata.getFile() != null && metadata.getFile().isFile() )
303 {
304 try
305 {
306 Map<String, ?> options = Collections.singletonMap( MetadataReader.IS_STRICT, Boolean.FALSE );
307
308 Metadata repoMetadata = metadataReader.read( metadata.getFile(), options );
309
310 mergeMetadata( versions, repoMetadata, repository );
311 }
312 catch ( IOException e )
313 {
314 invalidMetadata( session, trace, metadata, repository, e );
315 }
316 }
317 }
318
319 private void invalidMetadata( RepositorySystemSession session, RequestTrace trace,
320 org.eclipse.aether.metadata.Metadata metadata, ArtifactRepository repository,
321 Exception exception )
322 {
323 RepositoryListener listener = session.getRepositoryListener();
324 if ( listener != null )
325 {
326 RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_INVALID );
327 event.setTrace( trace );
328 event.setMetadata( metadata );
329 event.setException( exception );
330 event.setRepository( repository );
331 listener.metadataInvalid( event.build() );
332 }
333 }
334
335 private void mergeMetadata( Versions versions, Metadata source, ArtifactRepository repository )
336 {
337 Versioning versioning = source.getVersioning();
338 if ( versioning != null )
339 {
340 String timestamp = StringUtils.clean( versioning.getLastUpdated() );
341
342 if ( StringUtils.isNotEmpty( versioning.getRelease() )
343 && timestamp.compareTo( versions.releaseTimestamp ) > 0 )
344 {
345 versions.releaseVersion = versioning.getRelease();
346 versions.releaseTimestamp = timestamp;
347 versions.releaseRepository = repository;
348 }
349
350 if ( StringUtils.isNotEmpty( versioning.getLatest() )
351 && timestamp.compareTo( versions.latestTimestamp ) > 0 )
352 {
353 versions.latestVersion = versioning.getLatest();
354 versions.latestTimestamp = timestamp;
355 versions.latestRepository = repository;
356 }
357
358 for ( String version : versioning.getVersions() )
359 {
360 if ( !versions.versions.containsKey( version ) )
361 {
362 versions.versions.put( version, repository );
363 }
364 }
365 }
366 }
367
368 private PluginVersionResult resolveFromProject( PluginVersionRequest request )
369 {
370 PluginVersionResult result = null;
371
372 if ( request.getPom() != null && request.getPom().getBuild() != null )
373 {
374 Build build = request.getPom().getBuild();
375
376 result = resolveFromProject( request, build.getPlugins() );
377
378 if ( result == null && build.getPluginManagement() != null )
379 {
380 result = resolveFromProject( request, build.getPluginManagement().getPlugins() );
381 }
382 }
383
384 return result;
385 }
386
387 private PluginVersionResult resolveFromProject( PluginVersionRequest request, List<Plugin> plugins )
388 {
389 for ( Plugin plugin : plugins )
390 {
391 if ( request.getGroupId().equals( plugin.getGroupId() )
392 && request.getArtifactId().equals( plugin.getArtifactId() ) )
393 {
394 if ( plugin.getVersion() != null )
395 {
396 return new DefaultPluginVersionResult( plugin.getVersion() );
397 }
398 else
399 {
400 return null;
401 }
402 }
403 }
404 return null;
405 }
406
407 @SuppressWarnings( "unchecked" )
408 private ConcurrentMap<Key, PluginVersionResult> getCache( SessionData data )
409 {
410 ConcurrentMap<Key, PluginVersionResult> cache =
411 ( ConcurrentMap<Key, PluginVersionResult> ) data.get( CACHE_KEY );
412 while ( cache == null )
413 {
414 cache = new ConcurrentHashMap<>( 256 );
415 if ( data.set( CACHE_KEY, null, cache ) )
416 {
417 break;
418 }
419 cache = ( ConcurrentMap<Key, PluginVersionResult> ) data.get( CACHE_KEY );
420 }
421 return cache;
422 }
423
424 private static Key getKey( PluginVersionRequest request )
425 {
426 return new Key( request.getGroupId(), request.getArtifactId(), request.getRepositories() );
427 }
428
429 static class Key
430 {
431 final String groupId;
432 final String artifactId;
433 final List<RemoteRepository> repositories;
434 final int hash;
435
436 Key( String groupId, String artifactId, List<RemoteRepository> repositories )
437 {
438 this.groupId = groupId;
439 this.artifactId = artifactId;
440 this.repositories = repositories;
441 this.hash = Objects.hash( groupId, artifactId, repositories );
442 }
443
444 @Override
445 public boolean equals( Object o )
446 {
447 if ( this == o )
448 {
449 return true;
450 }
451 if ( o == null || getClass() != o.getClass() )
452 {
453 return false;
454 }
455 Key key = ( Key ) o;
456 return groupId.equals( key.groupId )
457 && artifactId.equals( key.artifactId )
458 && repositories.equals( key.repositories );
459 }
460
461 @Override
462 public int hashCode()
463 {
464 return hash;
465 }
466 }
467
468 static class Versions
469 {
470
471 String releaseVersion = "";
472
473 String releaseTimestamp = "";
474
475 ArtifactRepository releaseRepository;
476
477 String latestVersion = "";
478
479 String latestTimestamp = "";
480
481 ArtifactRepository latestRepository;
482
483 Map<String, ArtifactRepository> versions = new LinkedHashMap<>();
484
485 }
486
487 }