001package 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
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.TreeSet;
029
030import org.apache.maven.artifact.repository.metadata.Metadata;
031import org.apache.maven.artifact.repository.metadata.Versioning;
032import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
033import org.apache.maven.model.Build;
034import org.apache.maven.model.Plugin;
035import org.apache.maven.plugin.MavenPluginManager;
036import org.apache.maven.plugin.PluginResolutionException;
037import org.apache.maven.plugin.descriptor.PluginDescriptor;
038import org.apache.maven.plugin.version.PluginVersionRequest;
039import org.apache.maven.plugin.version.PluginVersionResolutionException;
040import org.apache.maven.plugin.version.PluginVersionResolver;
041import org.apache.maven.plugin.version.PluginVersionResult;
042import org.codehaus.plexus.component.annotations.Component;
043import org.codehaus.plexus.component.annotations.Requirement;
044import org.codehaus.plexus.logging.Logger;
045import org.codehaus.plexus.util.StringUtils;
046import org.eclipse.aether.RepositoryEvent.EventType;
047import org.eclipse.aether.RepositoryEvent;
048import org.eclipse.aether.RepositoryListener;
049import org.eclipse.aether.RepositorySystem;
050import org.eclipse.aether.RepositorySystemSession;
051import org.eclipse.aether.RequestTrace;
052import org.eclipse.aether.metadata.DefaultMetadata;
053import org.eclipse.aether.repository.ArtifactRepository;
054import org.eclipse.aether.repository.RemoteRepository;
055import org.eclipse.aether.resolution.MetadataRequest;
056import org.eclipse.aether.resolution.MetadataResult;
057import org.eclipse.aether.util.version.GenericVersionScheme;
058import org.eclipse.aether.version.InvalidVersionSpecificationException;
059import org.eclipse.aether.version.Version;
060import 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 )
069public 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<>();
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<>( Collections.reverseOrder() );
180            TreeSet<Version> snapshots = new TreeSet<>( 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                    // ignore
200                }
201            }
202
203            for ( Version v : releases )
204            {
205                String ver = v.toString();
206                if ( isCompatible( request, ver ) )
207                {
208                    version = ver;
209                    repo = versions.versions.get( version );
210                    break;
211                }
212            }
213
214            if ( version == null )
215            {
216                for ( Version v : snapshots )
217                {
218                    String ver = v.toString();
219                    if ( isCompatible( request, ver ) )
220                    {
221                        version = ver;
222                        repo = versions.versions.get( version );
223                        break;
224                    }
225                }
226            }
227        }
228
229        if ( version != null )
230        {
231            result.setVersion( version );
232            result.setRepository( repo );
233        }
234        else
235        {
236            throw new PluginVersionResolutionException( request.getGroupId(), request.getArtifactId(),
237                                                        request.getRepositorySession().getLocalRepository(),
238                                                        request.getRepositories(),
239                                                        "Plugin not found in any plugin repository" );
240        }
241    }
242
243    private boolean isCompatible( PluginVersionRequest request, String version )
244    {
245        Plugin plugin = new Plugin();
246        plugin.setGroupId( request.getGroupId() );
247        plugin.setArtifactId( request.getArtifactId() );
248        plugin.setVersion( version );
249
250        PluginDescriptor pluginDescriptor;
251
252        try
253        {
254            pluginDescriptor =
255                pluginManager.getPluginDescriptor( plugin, request.getRepositories(), request.getRepositorySession() );
256        }
257        catch ( PluginResolutionException e )
258        {
259            logger.debug( "Ignoring unresolvable plugin version " + version, e );
260            return false;
261        }
262        catch ( Exception e )
263        {
264            // ignore for now and delay failure to higher level processing
265            return true;
266        }
267
268        try
269        {
270            pluginManager.checkRequiredMavenVersion( pluginDescriptor );
271        }
272        catch ( Exception e )
273        {
274            logger.debug( "Ignoring incompatible plugin version " + version + ": " + e.getMessage() );
275            return false;
276        }
277
278        return true;
279    }
280
281    private void mergeMetadata( RepositorySystemSession session, RequestTrace trace, Versions versions,
282                                org.eclipse.aether.metadata.Metadata metadata, ArtifactRepository repository )
283    {
284        if ( metadata != null && metadata.getFile() != null && metadata.getFile().isFile() )
285        {
286            try
287            {
288                Map<String, ?> options = Collections.singletonMap( MetadataReader.IS_STRICT, Boolean.FALSE );
289
290                Metadata repoMetadata = metadataReader.read( metadata.getFile(), options );
291
292                mergeMetadata( versions, repoMetadata, repository );
293            }
294            catch ( IOException e )
295            {
296                invalidMetadata( session, trace, metadata, repository, e );
297            }
298        }
299    }
300
301    private void invalidMetadata( RepositorySystemSession session, RequestTrace trace,
302                                  org.eclipse.aether.metadata.Metadata metadata, ArtifactRepository repository,
303                                  Exception exception )
304    {
305        RepositoryListener listener = session.getRepositoryListener();
306        if ( listener != null )
307        {
308            RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_INVALID );
309            event.setTrace( trace );
310            event.setMetadata( metadata );
311            event.setException( exception );
312            event.setRepository( repository );
313            listener.metadataInvalid( event.build() );
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<>();
405
406    }
407
408}