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    }