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 }