View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.repository.internal;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.FileInputStream;
26  import java.io.InputStream;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Objects;
33  
34  import org.apache.maven.artifact.ArtifactUtils;
35  import org.apache.maven.artifact.repository.metadata.Versioning;
36  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
37  import org.eclipse.aether.RepositoryEvent;
38  import org.eclipse.aether.RepositoryEvent.EventType;
39  import org.eclipse.aether.RepositorySystemSession;
40  import org.eclipse.aether.RequestTrace;
41  import org.eclipse.aether.SyncContext;
42  import org.eclipse.aether.impl.MetadataResolver;
43  import org.eclipse.aether.impl.RepositoryEventDispatcher;
44  import org.eclipse.aether.impl.VersionRangeResolver;
45  import org.eclipse.aether.metadata.DefaultMetadata;
46  import org.eclipse.aether.metadata.Metadata;
47  import org.eclipse.aether.repository.ArtifactRepository;
48  import org.eclipse.aether.repository.RemoteRepository;
49  import org.eclipse.aether.repository.WorkspaceReader;
50  import org.eclipse.aether.resolution.MetadataRequest;
51  import org.eclipse.aether.resolution.MetadataResult;
52  import org.eclipse.aether.resolution.VersionRangeRequest;
53  import org.eclipse.aether.resolution.VersionRangeResolutionException;
54  import org.eclipse.aether.resolution.VersionRangeResult;
55  import org.eclipse.aether.spi.locator.Service;
56  import org.eclipse.aether.spi.locator.ServiceLocator;
57  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
58  import org.eclipse.aether.util.version.GenericVersionScheme;
59  import org.eclipse.aether.version.InvalidVersionSpecificationException;
60  import org.eclipse.aether.version.Version;
61  import org.eclipse.aether.version.VersionConstraint;
62  import org.eclipse.aether.version.VersionRange;
63  import org.eclipse.aether.version.VersionScheme;
64  
65  /**
66   * @author Benjamin Bentmann
67   */
68  @Named
69  @Singleton
70  public class DefaultVersionRangeResolver implements VersionRangeResolver, Service {
71  
72      private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
73  
74      private MetadataResolver metadataResolver;
75  
76      private SyncContextFactory syncContextFactory;
77  
78      private RepositoryEventDispatcher repositoryEventDispatcher;
79  
80      @Deprecated
81      public DefaultVersionRangeResolver() {
82          // enable default constructor
83      }
84  
85      @Inject
86      public DefaultVersionRangeResolver(
87              MetadataResolver metadataResolver,
88              SyncContextFactory syncContextFactory,
89              RepositoryEventDispatcher repositoryEventDispatcher) {
90          setMetadataResolver(metadataResolver);
91          setSyncContextFactory(syncContextFactory);
92          setRepositoryEventDispatcher(repositoryEventDispatcher);
93      }
94  
95      @Deprecated
96      public void initService(ServiceLocator locator) {
97          setMetadataResolver(locator.getService(MetadataResolver.class));
98          setSyncContextFactory(locator.getService(SyncContextFactory.class));
99          setRepositoryEventDispatcher(locator.getService(RepositoryEventDispatcher.class));
100     }
101 
102     public DefaultVersionRangeResolver setMetadataResolver(MetadataResolver metadataResolver) {
103         this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
104         return this;
105     }
106 
107     public DefaultVersionRangeResolver setSyncContextFactory(SyncContextFactory syncContextFactory) {
108         this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
109         return this;
110     }
111 
112     public DefaultVersionRangeResolver setRepositoryEventDispatcher(
113             RepositoryEventDispatcher repositoryEventDispatcher) {
114         this.repositoryEventDispatcher =
115                 Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
116         return this;
117     }
118 
119     @Override
120     public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request)
121             throws VersionRangeResolutionException {
122         VersionRangeResult result = new VersionRangeResult(request);
123 
124         VersionScheme versionScheme = new GenericVersionScheme();
125 
126         VersionConstraint versionConstraint;
127         try {
128             versionConstraint =
129                     versionScheme.parseVersionConstraint(request.getArtifact().getVersion());
130         } catch (InvalidVersionSpecificationException e) {
131             result.addException(e);
132             throw new VersionRangeResolutionException(result);
133         }
134 
135         result.setVersionConstraint(versionConstraint);
136 
137         if (versionConstraint.getRange() == null) {
138             result.addVersion(versionConstraint.getVersion());
139         } else {
140             VersionRange.Bound lowerBound = versionConstraint.getRange().getLowerBound();
141             if (lowerBound != null
142                     && lowerBound.equals(versionConstraint.getRange().getUpperBound())) {
143                 result.addVersion(lowerBound.getVersion());
144             } else {
145                 Map<String, ArtifactRepository> versionIndex = getVersions(session, result, request);
146 
147                 List<Version> versions = new ArrayList<>();
148                 for (Map.Entry<String, ArtifactRepository> v : versionIndex.entrySet()) {
149                     try {
150                         Version ver = versionScheme.parseVersion(v.getKey());
151                         if (versionConstraint.containsVersion(ver)) {
152                             versions.add(ver);
153                             result.setRepository(ver, v.getValue());
154                         }
155                     } catch (InvalidVersionSpecificationException e) {
156                         result.addException(e);
157                     }
158                 }
159 
160                 Collections.sort(versions);
161                 result.setVersions(versions);
162             }
163         }
164 
165         return result;
166     }
167 
168     private Map<String, ArtifactRepository> getVersions(
169             RepositorySystemSession session, VersionRangeResult result, VersionRangeRequest request) {
170         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
171 
172         Map<String, ArtifactRepository> versionIndex = new HashMap<>();
173 
174         Metadata metadata = new DefaultMetadata(
175                 request.getArtifact().getGroupId(),
176                 request.getArtifact().getArtifactId(),
177                 MAVEN_METADATA_XML,
178                 Metadata.Nature.RELEASE_OR_SNAPSHOT);
179 
180         List<MetadataRequest> metadataRequests =
181                 new ArrayList<>(request.getRepositories().size());
182 
183         metadataRequests.add(new MetadataRequest(metadata, null, request.getRequestContext()));
184 
185         for (RemoteRepository repository : request.getRepositories()) {
186             MetadataRequest metadataRequest = new MetadataRequest(metadata, repository, request.getRequestContext());
187             metadataRequest.setDeleteLocalCopyIfMissing(true);
188             metadataRequest.setTrace(trace);
189             metadataRequests.add(metadataRequest);
190         }
191 
192         List<MetadataResult> metadataResults = metadataResolver.resolveMetadata(session, metadataRequests);
193 
194         WorkspaceReader workspace = session.getWorkspaceReader();
195         if (workspace != null) {
196             List<String> versions = workspace.findVersions(request.getArtifact());
197             for (String version : versions) {
198                 versionIndex.put(version, workspace.getRepository());
199             }
200         }
201 
202         for (MetadataResult metadataResult : metadataResults) {
203             result.addException(metadataResult.getException());
204 
205             ArtifactRepository repository = metadataResult.getRequest().getRepository();
206             if (repository == null) {
207                 repository = session.getLocalRepository();
208             }
209 
210             Versioning versioning = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
211 
212             versioning = filterVersionsByRepositoryType(
213                     versioning, metadataResult.getRequest().getRepository());
214 
215             for (String version : versioning.getVersions()) {
216                 if (!versionIndex.containsKey(version)) {
217                     versionIndex.put(version, repository);
218                 }
219             }
220         }
221 
222         return versionIndex;
223     }
224 
225     private Versioning readVersions(
226             RepositorySystemSession session,
227             RequestTrace trace,
228             Metadata metadata,
229             ArtifactRepository repository,
230             VersionRangeResult result) {
231         Versioning versioning = null;
232         try {
233             if (metadata != null) {
234                 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
235                     syncContext.acquire(null, Collections.singleton(metadata));
236 
237                     if (metadata.getFile() != null && metadata.getFile().exists()) {
238                         try (InputStream in = new FileInputStream(metadata.getFile())) {
239                             versioning =
240                                     new MetadataXpp3Reader().read(in, false).getVersioning();
241                         }
242                     }
243                 }
244             }
245         } catch (Exception e) {
246             invalidMetadata(session, trace, metadata, repository, e);
247             result.addException(e);
248         }
249 
250         return (versioning != null) ? versioning : new Versioning();
251     }
252 
253     private Versioning filterVersionsByRepositoryType(Versioning versioning, RemoteRepository remoteRepository) {
254         if (remoteRepository == null) {
255             return versioning;
256         }
257 
258         Versioning filteredVersions = versioning.clone();
259 
260         for (String version : versioning.getVersions()) {
261             if (!remoteRepository.getPolicy(ArtifactUtils.isSnapshot(version)).isEnabled()) {
262                 filteredVersions.removeVersion(version);
263             }
264         }
265 
266         return filteredVersions;
267     }
268 
269     private void invalidMetadata(
270             RepositorySystemSession session,
271             RequestTrace trace,
272             Metadata metadata,
273             ArtifactRepository repository,
274             Exception exception) {
275         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
276         event.setTrace(trace);
277         event.setMetadata(metadata);
278         event.setException(exception);
279         event.setRepository(repository);
280 
281         repositoryEventDispatcher.dispatch(event.build());
282     }
283 }