001 package org.apache.maven.plugin.version.internal; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import java.io.IOException; 023 import java.util.ArrayList; 024 import java.util.Collections; 025 import java.util.LinkedHashMap; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.TreeSet; 029 030 import org.apache.maven.artifact.repository.metadata.Metadata; 031 import org.apache.maven.artifact.repository.metadata.Versioning; 032 import org.apache.maven.artifact.repository.metadata.io.MetadataReader; 033 import org.apache.maven.model.Build; 034 import org.apache.maven.model.Plugin; 035 import org.apache.maven.plugin.MavenPluginManager; 036 import org.apache.maven.plugin.PluginResolutionException; 037 import org.apache.maven.plugin.descriptor.PluginDescriptor; 038 import org.apache.maven.plugin.version.PluginVersionRequest; 039 import org.apache.maven.plugin.version.PluginVersionResolutionException; 040 import org.apache.maven.plugin.version.PluginVersionResolver; 041 import org.apache.maven.plugin.version.PluginVersionResult; 042 import org.codehaus.plexus.component.annotations.Component; 043 import org.codehaus.plexus.component.annotations.Requirement; 044 import org.codehaus.plexus.logging.Logger; 045 import org.codehaus.plexus.util.StringUtils; 046 import org.sonatype.aether.RepositoryEvent.EventType; 047 import org.sonatype.aether.RepositoryListener; 048 import org.sonatype.aether.RepositorySystem; 049 import org.sonatype.aether.RepositorySystemSession; 050 import org.sonatype.aether.RequestTrace; 051 import org.sonatype.aether.repository.ArtifactRepository; 052 import org.sonatype.aether.repository.RemoteRepository; 053 import org.sonatype.aether.resolution.MetadataRequest; 054 import org.sonatype.aether.resolution.MetadataResult; 055 import org.sonatype.aether.util.DefaultRequestTrace; 056 import org.sonatype.aether.util.listener.DefaultRepositoryEvent; 057 import org.sonatype.aether.util.metadata.DefaultMetadata; 058 import org.sonatype.aether.util.version.GenericVersionScheme; 059 import org.sonatype.aether.version.InvalidVersionSpecificationException; 060 import org.sonatype.aether.version.Version; 061 import org.sonatype.aether.version.VersionScheme; 062 063 /** 064 * Resolves a version for a plugin. 065 * 066 * @since 3.0 067 * @author Benjamin Bentmann 068 */ 069 @Component( role = PluginVersionResolver.class ) 070 public class DefaultPluginVersionResolver 071 implements PluginVersionResolver 072 { 073 074 private static final String REPOSITORY_CONTEXT = "plugin"; 075 076 @Requirement 077 private Logger logger; 078 079 @Requirement 080 private RepositorySystem repositorySystem; 081 082 @Requirement 083 private MetadataReader metadataReader; 084 085 @Requirement 086 private MavenPluginManager pluginManager; 087 088 public PluginVersionResult resolve( PluginVersionRequest request ) 089 throws PluginVersionResolutionException 090 { 091 logger.debug( "Resolving plugin version for " + request.getGroupId() + ":" + request.getArtifactId() ); 092 093 PluginVersionResult result = resolveFromProject( request ); 094 095 if ( result == null ) 096 { 097 result = resolveFromRepository( request ); 098 099 if ( logger.isDebugEnabled() ) 100 { 101 logger.debug( "Resolved plugin version for " + request.getGroupId() + ":" + request.getArtifactId() 102 + " to " + result.getVersion() + " from repository " + result.getRepository() ); 103 } 104 } 105 else if ( logger.isDebugEnabled() ) 106 { 107 logger.debug( "Resolved plugin version for " + request.getGroupId() + ":" + request.getArtifactId() 108 + " to " + result.getVersion() + " from POM " + request.getPom() ); 109 } 110 111 return result; 112 } 113 114 private PluginVersionResult resolveFromRepository( PluginVersionRequest request ) 115 throws PluginVersionResolutionException 116 { 117 RequestTrace trace = DefaultRequestTrace.newChild( null, request ); 118 119 DefaultPluginVersionResult result = new DefaultPluginVersionResult(); 120 121 org.sonatype.aether.metadata.Metadata metadata = 122 new DefaultMetadata( request.getGroupId(), request.getArtifactId(), "maven-metadata.xml", 123 DefaultMetadata.Nature.RELEASE_OR_SNAPSHOT ); 124 125 List<MetadataRequest> requests = new ArrayList<MetadataRequest>(); 126 127 requests.add( new MetadataRequest( metadata, null, REPOSITORY_CONTEXT ).setTrace( trace ) ); 128 129 for ( RemoteRepository repository : request.getRepositories() ) 130 { 131 requests.add( new MetadataRequest( metadata, repository, REPOSITORY_CONTEXT ).setTrace( trace ) ); 132 } 133 134 List<MetadataResult> results = repositorySystem.resolveMetadata( request.getRepositorySession(), requests ); 135 136 Versions versions = new Versions(); 137 138 for ( MetadataResult res : results ) 139 { 140 ArtifactRepository repository = res.getRequest().getRepository(); 141 if ( repository == null ) 142 { 143 repository = request.getRepositorySession().getLocalRepository(); 144 } 145 146 mergeMetadata( request.getRepositorySession(), trace, versions, res.getMetadata(), repository ); 147 } 148 149 selectVersion( result, request, versions ); 150 151 return result; 152 } 153 154 private void selectVersion( DefaultPluginVersionResult result, PluginVersionRequest request, Versions versions ) 155 throws PluginVersionResolutionException 156 { 157 String version = null; 158 ArtifactRepository repo = null; 159 160 if ( StringUtils.isNotEmpty( versions.releaseVersion ) ) 161 { 162 version = versions.releaseVersion; 163 repo = versions.releaseRepository; 164 } 165 else if ( StringUtils.isNotEmpty( versions.latestVersion ) ) 166 { 167 version = versions.latestVersion; 168 repo = versions.latestRepository; 169 } 170 if ( version != null && !isCompatible( request, version ) ) 171 { 172 versions.versions.remove( version ); 173 version = null; 174 } 175 176 if ( version == null ) 177 { 178 VersionScheme versionScheme = new GenericVersionScheme(); 179 180 TreeSet<Version> releases = new TreeSet<Version>( Collections.reverseOrder() ); 181 TreeSet<Version> snapshots = new TreeSet<Version>( Collections.reverseOrder() ); 182 183 for ( String ver : versions.versions.keySet() ) 184 { 185 try 186 { 187 Version v = versionScheme.parseVersion( ver ); 188 189 if ( ver.endsWith( "-SNAPSHOT" ) ) 190 { 191 snapshots.add( v ); 192 } 193 else 194 { 195 releases.add( v ); 196 } 197 } 198 catch ( InvalidVersionSpecificationException e ) 199 { 200 continue; 201 } 202 } 203 204 for ( Version v : releases ) 205 { 206 String ver = v.toString(); 207 if ( isCompatible( request, ver ) ) 208 { 209 version = ver; 210 repo = versions.versions.get( version ); 211 break; 212 } 213 } 214 215 if ( version == null ) 216 { 217 for ( Version v : snapshots ) 218 { 219 String ver = v.toString(); 220 if ( isCompatible( request, ver ) ) 221 { 222 version = ver; 223 repo = versions.versions.get( version ); 224 break; 225 } 226 } 227 } 228 } 229 230 if ( version != null ) 231 { 232 result.setVersion( version ); 233 result.setRepository( repo ); 234 } 235 else 236 { 237 throw new PluginVersionResolutionException( request.getGroupId(), request.getArtifactId(), 238 request.getRepositorySession().getLocalRepository(), 239 request.getRepositories(), 240 "Plugin not found in any plugin repository" ); 241 } 242 } 243 244 private boolean isCompatible( PluginVersionRequest request, String version ) 245 { 246 Plugin plugin = new Plugin(); 247 plugin.setGroupId( request.getGroupId() ); 248 plugin.setArtifactId( request.getArtifactId() ); 249 plugin.setVersion( version ); 250 251 PluginDescriptor pluginDescriptor; 252 253 try 254 { 255 pluginDescriptor = 256 pluginManager.getPluginDescriptor( plugin, request.getRepositories(), request.getRepositorySession() ); 257 } 258 catch ( PluginResolutionException e ) 259 { 260 logger.debug( "Ignoring unresolvable plugin version " + version, e ); 261 return false; 262 } 263 catch ( Exception e ) 264 { 265 // ignore for now and delay failure to higher level processing 266 return true; 267 } 268 269 try 270 { 271 pluginManager.checkRequiredMavenVersion( pluginDescriptor ); 272 } 273 catch ( Exception e ) 274 { 275 logger.debug( "Ignoring incompatible plugin version " + version + ": " + e.getMessage() ); 276 return false; 277 } 278 279 return true; 280 } 281 282 private void mergeMetadata( RepositorySystemSession session, RequestTrace trace, Versions versions, 283 org.sonatype.aether.metadata.Metadata metadata, ArtifactRepository repository ) 284 { 285 if ( metadata != null && metadata.getFile() != null && metadata.getFile().isFile() ) 286 { 287 try 288 { 289 Map<String, ?> options = Collections.singletonMap( MetadataReader.IS_STRICT, Boolean.FALSE ); 290 291 Metadata repoMetadata = metadataReader.read( metadata.getFile(), options ); 292 293 mergeMetadata( versions, repoMetadata, repository ); 294 } 295 catch ( IOException e ) 296 { 297 invalidMetadata( session, trace, metadata, repository, e ); 298 } 299 } 300 } 301 302 private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, 303 org.sonatype.aether.metadata.Metadata metadata, ArtifactRepository repository, 304 Exception exception ) 305 { 306 RepositoryListener listener = session.getRepositoryListener(); 307 if ( listener != null ) 308 { 309 DefaultRepositoryEvent event = new DefaultRepositoryEvent( EventType.METADATA_INVALID, session, trace ); 310 event.setMetadata( metadata ); 311 event.setException( exception ); 312 event.setRepository( repository ); 313 listener.metadataInvalid( event ); 314 } 315 } 316 317 private void mergeMetadata( Versions versions, Metadata source, ArtifactRepository repository ) 318 { 319 Versioning versioning = source.getVersioning(); 320 if ( versioning != null ) 321 { 322 String timestamp = StringUtils.clean( versioning.getLastUpdated() ); 323 324 if ( StringUtils.isNotEmpty( versioning.getRelease() ) 325 && timestamp.compareTo( versions.releaseTimestamp ) > 0 ) 326 { 327 versions.releaseVersion = versioning.getRelease(); 328 versions.releaseTimestamp = timestamp; 329 versions.releaseRepository = repository; 330 } 331 332 if ( StringUtils.isNotEmpty( versioning.getLatest() ) 333 && timestamp.compareTo( versions.latestTimestamp ) > 0 ) 334 { 335 versions.latestVersion = versioning.getLatest(); 336 versions.latestTimestamp = timestamp; 337 versions.latestRepository = repository; 338 } 339 340 for ( String version : versioning.getVersions() ) 341 { 342 if ( !versions.versions.containsKey( version ) ) 343 { 344 versions.versions.put( version, repository ); 345 } 346 } 347 } 348 } 349 350 private PluginVersionResult resolveFromProject( PluginVersionRequest request ) 351 { 352 PluginVersionResult result = null; 353 354 if ( request.getPom() != null && request.getPom().getBuild() != null ) 355 { 356 Build build = request.getPom().getBuild(); 357 358 result = resolveFromProject( request, build.getPlugins() ); 359 360 if ( result == null && build.getPluginManagement() != null ) 361 { 362 result = resolveFromProject( request, build.getPluginManagement().getPlugins() ); 363 } 364 } 365 366 return result; 367 } 368 369 private PluginVersionResult resolveFromProject( PluginVersionRequest request, List<Plugin> plugins ) 370 { 371 for ( Plugin plugin : plugins ) 372 { 373 if ( request.getGroupId().equals( plugin.getGroupId() ) 374 && request.getArtifactId().equals( plugin.getArtifactId() ) ) 375 { 376 if ( plugin.getVersion() != null ) 377 { 378 return new DefaultPluginVersionResult( plugin.getVersion() ); 379 } 380 else 381 { 382 return null; 383 } 384 } 385 } 386 return null; 387 } 388 389 static class Versions 390 { 391 392 String releaseVersion = ""; 393 394 String releaseTimestamp = ""; 395 396 ArtifactRepository releaseRepository; 397 398 String latestVersion = ""; 399 400 String latestTimestamp = ""; 401 402 ArtifactRepository latestRepository; 403 404 Map<String, ArtifactRepository> versions = new LinkedHashMap<String, ArtifactRepository>(); 405 406 } 407 408 }