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