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<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}