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    }