001package org.apache.maven.repository.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 org.apache.commons.lang3.Validate;
023import org.apache.maven.artifact.repository.metadata.Versioning;
024import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
025import org.codehaus.plexus.component.annotations.Component;
026import org.codehaus.plexus.component.annotations.Requirement;
027import org.codehaus.plexus.util.IOUtil;
028import org.eclipse.aether.RepositoryEvent;
029import org.eclipse.aether.RepositoryEvent.EventType;
030import org.eclipse.aether.RepositorySystemSession;
031import org.eclipse.aether.RequestTrace;
032import org.eclipse.aether.SyncContext;
033import org.eclipse.aether.impl.MetadataResolver;
034import org.eclipse.aether.impl.RepositoryEventDispatcher;
035import org.eclipse.aether.impl.SyncContextFactory;
036import org.eclipse.aether.impl.VersionRangeResolver;
037import org.eclipse.aether.metadata.DefaultMetadata;
038import org.eclipse.aether.metadata.Metadata;
039import org.eclipse.aether.repository.ArtifactRepository;
040import org.eclipse.aether.repository.RemoteRepository;
041import org.eclipse.aether.repository.WorkspaceReader;
042import org.eclipse.aether.resolution.MetadataRequest;
043import org.eclipse.aether.resolution.MetadataResult;
044import org.eclipse.aether.resolution.VersionRangeRequest;
045import org.eclipse.aether.resolution.VersionRangeResolutionException;
046import org.eclipse.aether.resolution.VersionRangeResult;
047import org.eclipse.aether.spi.locator.Service;
048import org.eclipse.aether.spi.locator.ServiceLocator;
049import org.eclipse.aether.spi.log.Logger;
050import org.eclipse.aether.spi.log.LoggerFactory;
051import org.eclipse.aether.spi.log.NullLoggerFactory;
052import org.eclipse.aether.util.version.GenericVersionScheme;
053import org.eclipse.aether.version.InvalidVersionSpecificationException;
054import org.eclipse.aether.version.Version;
055import org.eclipse.aether.version.VersionConstraint;
056import org.eclipse.aether.version.VersionScheme;
057
058import javax.inject.Inject;
059import javax.inject.Named;
060import java.io.FileInputStream;
061import java.util.ArrayList;
062import java.util.Collections;
063import java.util.HashMap;
064import java.util.List;
065import java.util.Map;
066
067/**
068 * @author Benjamin Bentmann
069 */
070@Named
071@Component( role = VersionRangeResolver.class )
072public class DefaultVersionRangeResolver
073    implements VersionRangeResolver, Service
074{
075
076    private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
077
078    @SuppressWarnings( "unused" )
079    @Requirement( role = LoggerFactory.class )
080    private Logger logger = NullLoggerFactory.LOGGER;
081
082    @Requirement
083    private MetadataResolver metadataResolver;
084
085    @Requirement
086    private SyncContextFactory syncContextFactory;
087
088    @Requirement
089    private RepositoryEventDispatcher repositoryEventDispatcher;
090
091    public DefaultVersionRangeResolver()
092    {
093        // enable default constructor
094    }
095
096    @Inject
097    DefaultVersionRangeResolver( MetadataResolver metadataResolver, SyncContextFactory syncContextFactory,
098                                 RepositoryEventDispatcher repositoryEventDispatcher, LoggerFactory loggerFactory )
099    {
100        setMetadataResolver( metadataResolver );
101        setSyncContextFactory( syncContextFactory );
102        setLoggerFactory( loggerFactory );
103        setRepositoryEventDispatcher( repositoryEventDispatcher );
104    }
105
106    public void initService( ServiceLocator locator )
107    {
108        setLoggerFactory( locator.getService( LoggerFactory.class ) );
109        setMetadataResolver( locator.getService( MetadataResolver.class ) );
110        setSyncContextFactory( locator.getService( SyncContextFactory.class ) );
111        setRepositoryEventDispatcher( locator.getService( RepositoryEventDispatcher.class ) );
112    }
113
114    public DefaultVersionRangeResolver setLoggerFactory( LoggerFactory loggerFactory )
115    {
116        this.logger = NullLoggerFactory.getSafeLogger( loggerFactory, getClass() );
117        return this;
118    }
119
120    void setLogger( LoggerFactory loggerFactory )
121    {
122        // plexus support
123        setLoggerFactory( loggerFactory );
124    }
125
126    public DefaultVersionRangeResolver setMetadataResolver( MetadataResolver metadataResolver )
127    {
128        this.metadataResolver = Validate.notNull( metadataResolver, "metadataResolver cannot be null" );
129        return this;
130    }
131
132    public DefaultVersionRangeResolver setSyncContextFactory( SyncContextFactory syncContextFactory )
133    {
134        this.syncContextFactory = Validate.notNull( syncContextFactory, "syncContextFactory cannot be null" );
135        return this;
136    }
137
138    public DefaultVersionRangeResolver setRepositoryEventDispatcher(
139        RepositoryEventDispatcher repositoryEventDispatcher )
140    {
141        this.repositoryEventDispatcher = Validate.notNull( repositoryEventDispatcher,
142            "repositoryEventDispatcher cannot be null" );
143        return this;
144    }
145
146    public VersionRangeResult resolveVersionRange( RepositorySystemSession session, VersionRangeRequest request )
147        throws VersionRangeResolutionException
148    {
149        VersionRangeResult result = new VersionRangeResult( request );
150
151        VersionScheme versionScheme = new GenericVersionScheme();
152
153        VersionConstraint versionConstraint;
154        try
155        {
156            versionConstraint = versionScheme.parseVersionConstraint( request.getArtifact().getVersion() );
157        }
158        catch ( InvalidVersionSpecificationException e )
159        {
160            result.addException( e );
161            throw new VersionRangeResolutionException( result );
162        }
163
164        result.setVersionConstraint( versionConstraint );
165
166        if ( versionConstraint.getRange() == null )
167        {
168            result.addVersion( versionConstraint.getVersion() );
169        }
170        else
171        {
172            Map<String, ArtifactRepository> versionIndex = getVersions( session, result, request );
173
174            List<Version> versions = new ArrayList<>();
175            for ( Map.Entry<String, ArtifactRepository> v : versionIndex.entrySet() )
176            {
177                try
178                {
179                    Version ver = versionScheme.parseVersion( v.getKey() );
180                    if ( versionConstraint.containsVersion( ver ) )
181                    {
182                        versions.add( ver );
183                        result.setRepository( ver, v.getValue() );
184                    }
185                }
186                catch ( InvalidVersionSpecificationException e )
187                {
188                    result.addException( e );
189                }
190            }
191
192            Collections.sort( versions );
193            result.setVersions( versions );
194        }
195
196        return result;
197    }
198
199    private Map<String, ArtifactRepository> getVersions( RepositorySystemSession session, VersionRangeResult result,
200                                                         VersionRangeRequest request )
201    {
202        RequestTrace trace = RequestTrace.newChild( request.getTrace(), request );
203
204        Map<String, ArtifactRepository> versionIndex = new HashMap<>();
205
206        Metadata metadata =
207            new DefaultMetadata( request.getArtifact().getGroupId(), request.getArtifact().getArtifactId(),
208                                 MAVEN_METADATA_XML, Metadata.Nature.RELEASE_OR_SNAPSHOT );
209
210        List<MetadataRequest> metadataRequests = new ArrayList<>( request.getRepositories().size() );
211
212        metadataRequests.add( new MetadataRequest( metadata, null, request.getRequestContext() ) );
213
214        for ( RemoteRepository repository : request.getRepositories() )
215        {
216            MetadataRequest metadataRequest = new MetadataRequest( metadata, repository, request.getRequestContext() );
217            metadataRequest.setDeleteLocalCopyIfMissing( true );
218            metadataRequest.setTrace( trace );
219            metadataRequests.add( metadataRequest );
220        }
221
222        List<MetadataResult> metadataResults = metadataResolver.resolveMetadata( session, metadataRequests );
223
224        WorkspaceReader workspace = session.getWorkspaceReader();
225        if ( workspace != null )
226        {
227            List<String> versions = workspace.findVersions( request.getArtifact() );
228            for ( String version : versions )
229            {
230                versionIndex.put( version, workspace.getRepository() );
231            }
232        }
233
234        for ( MetadataResult metadataResult : metadataResults )
235        {
236            result.addException( metadataResult.getException() );
237
238            ArtifactRepository repository = metadataResult.getRequest().getRepository();
239            if ( repository == null )
240            {
241                repository = session.getLocalRepository();
242            }
243
244            Versioning versioning = readVersions( session, trace, metadataResult.getMetadata(), repository, result );
245            for ( String version : versioning.getVersions() )
246            {
247                if ( !versionIndex.containsKey( version ) )
248                {
249                    versionIndex.put( version, repository );
250                }
251            }
252        }
253
254        return versionIndex;
255    }
256
257    private Versioning readVersions( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
258                                     ArtifactRepository repository, VersionRangeResult result )
259    {
260        Versioning versioning = null;
261
262        FileInputStream fis = null;
263        try
264        {
265            if ( metadata != null )
266            {
267
268                try ( SyncContext syncContext = syncContextFactory.newInstance( session, true ) )
269                {
270                    syncContext.acquire( null, Collections.singleton( metadata ) );
271
272                    if ( metadata.getFile() != null && metadata.getFile().exists() )
273                    {
274                        fis = new FileInputStream( metadata.getFile() );
275                        org.apache.maven.artifact.repository.metadata.Metadata m =
276                            new MetadataXpp3Reader().read( fis, false );
277                        versioning = m.getVersioning();
278                    }
279                }
280            }
281        }
282        catch ( Exception e )
283        {
284            invalidMetadata( session, trace, metadata, repository, e );
285            result.addException( e );
286        }
287        finally
288        {
289            IOUtil.close( fis );
290        }
291
292        return ( versioning != null ) ? versioning : new Versioning();
293    }
294
295    private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, Metadata metadata,
296                                  ArtifactRepository repository, Exception exception )
297    {
298        RepositoryEvent.Builder event = new RepositoryEvent.Builder( session, EventType.METADATA_INVALID );
299        event.setTrace( trace );
300        event.setMetadata( metadata );
301        event.setException( exception );
302        event.setRepository( repository );
303
304        repositoryEventDispatcher.dispatch( event.build() );
305    }
306
307}