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.InputStream;
26  import java.nio.file.Files;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Objects;
34  
35  import org.apache.maven.api.Constants;
36  import org.apache.maven.artifact.ArtifactUtils;
37  import org.apache.maven.artifact.repository.metadata.Versioning;
38  import org.apache.maven.metadata.v4.MetadataStaxReader;
39  import org.eclipse.aether.RepositoryEvent;
40  import org.eclipse.aether.RepositoryEvent.EventType;
41  import org.eclipse.aether.RepositorySystemSession;
42  import org.eclipse.aether.RequestTrace;
43  import org.eclipse.aether.SyncContext;
44  import org.eclipse.aether.impl.MetadataResolver;
45  import org.eclipse.aether.impl.RepositoryEventDispatcher;
46  import org.eclipse.aether.impl.VersionRangeResolver;
47  import org.eclipse.aether.metadata.DefaultMetadata;
48  import org.eclipse.aether.metadata.Metadata;
49  import org.eclipse.aether.repository.ArtifactRepository;
50  import org.eclipse.aether.repository.RemoteRepository;
51  import org.eclipse.aether.repository.WorkspaceReader;
52  import org.eclipse.aether.resolution.MetadataRequest;
53  import org.eclipse.aether.resolution.MetadataResult;
54  import org.eclipse.aether.resolution.VersionRangeRequest;
55  import org.eclipse.aether.resolution.VersionRangeResolutionException;
56  import org.eclipse.aether.resolution.VersionRangeResult;
57  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
58  import org.eclipse.aether.util.ConfigUtils;
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   * @deprecated since 4.0.0, use {@code maven-api-impl} jar instead/
67   */
68  @Named
69  @Singleton
70  @Deprecated(since = "4.0.0")
71  public class DefaultVersionRangeResolver implements VersionRangeResolver {
72  
73      private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
74  
75      private final MetadataResolver metadataResolver;
76      private final SyncContextFactory syncContextFactory;
77      private final RepositoryEventDispatcher repositoryEventDispatcher;
78      private final VersionScheme versionScheme;
79  
80      @Inject
81      public DefaultVersionRangeResolver(
82              MetadataResolver metadataResolver,
83              SyncContextFactory syncContextFactory,
84              RepositoryEventDispatcher repositoryEventDispatcher,
85              VersionScheme versionScheme) {
86          this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
87          this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
88          this.repositoryEventDispatcher =
89                  Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
90          this.versionScheme = Objects.requireNonNull(versionScheme, "versionScheme cannot be null");
91      }
92  
93      @Override
94      public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request)
95              throws VersionRangeResolutionException {
96          VersionRangeResult result = new VersionRangeResult(request);
97  
98          VersionConstraint versionConstraint;
99          try {
100             versionConstraint =
101                     versionScheme.parseVersionConstraint(request.getArtifact().getVersion());
102         } catch (InvalidVersionSpecificationException e) {
103             result.addException(e);
104             throw new VersionRangeResolutionException(result);
105         }
106 
107         result.setVersionConstraint(versionConstraint);
108 
109         if (versionConstraint.getRange() == null) {
110             result.addVersion(versionConstraint.getVersion());
111         } else {
112             VersionRange.Bound lowerBound = versionConstraint.getRange().getLowerBound();
113             VersionRange.Bound upperBound = versionConstraint.getRange().getUpperBound();
114             if (lowerBound != null
115                     && lowerBound.equals(versionConstraint.getRange().getUpperBound())) {
116                 result.addVersion(lowerBound.getVersion());
117             } else {
118                 Metadata.Nature wantedNature;
119                 String natureString = ConfigUtils.getString(
120                         session,
121                         Metadata.Nature.RELEASE_OR_SNAPSHOT.name(),
122                         Constants.MAVEN_VERSION_RANGE_RESOLVER_NATURE_OVERRIDE);
123                 if ("auto".equals(natureString)) {
124                     org.eclipse.aether.artifact.Artifact lowerArtifact = lowerBound != null
125                             ? request.getArtifact()
126                                     .setVersion(lowerBound.getVersion().toString())
127                             : null;
128                     org.eclipse.aether.artifact.Artifact upperArtifact = upperBound != null
129                             ? request.getArtifact()
130                                     .setVersion(upperBound.getVersion().toString())
131                             : null;
132 
133                     if (lowerArtifact != null && lowerArtifact.isSnapshot()
134                             || upperArtifact != null && upperArtifact.isSnapshot()) {
135                         wantedNature = Metadata.Nature.RELEASE_OR_SNAPSHOT;
136                     } else {
137                         wantedNature = Metadata.Nature.RELEASE;
138                     }
139                 } else {
140                     wantedNature = Metadata.Nature.valueOf(natureString.toUpperCase(Locale.ROOT));
141                 }
142 
143                 Map<String, ArtifactRepository> versionIndex = getVersions(session, result, request, wantedNature);
144 
145                 List<Version> versions = new ArrayList<>();
146                 for (Map.Entry<String, ArtifactRepository> v : versionIndex.entrySet()) {
147                     try {
148                         Version ver = versionScheme.parseVersion(v.getKey());
149                         if (versionConstraint.containsVersion(ver)) {
150                             versions.add(ver);
151                             result.setRepository(ver, v.getValue());
152                         }
153                     } catch (InvalidVersionSpecificationException e) {
154                         result.addException(e);
155                     }
156                 }
157 
158                 Collections.sort(versions);
159                 result.setVersions(versions);
160             }
161         }
162 
163         return result;
164     }
165 
166     private Map<String, ArtifactRepository> getVersions(
167             RepositorySystemSession session,
168             VersionRangeResult result,
169             VersionRangeRequest request,
170             Metadata.Nature wantedNature) {
171         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
172 
173         Map<String, ArtifactRepository> versionIndex = new HashMap<>();
174 
175         Metadata metadata = new DefaultMetadata(
176                 request.getArtifact().getGroupId(),
177                 request.getArtifact().getArtifactId(),
178                 MAVEN_METADATA_XML,
179                 wantedNature);
180 
181         List<MetadataRequest> metadataRequests =
182                 new ArrayList<>(request.getRepositories().size());
183 
184         metadataRequests.add(new MetadataRequest(metadata, null, request.getRequestContext()));
185 
186         for (RemoteRepository repository : request.getRepositories()) {
187             MetadataRequest metadataRequest = new MetadataRequest(metadata, repository, request.getRequestContext());
188             metadataRequest.setDeleteLocalCopyIfMissing(true);
189             metadataRequest.setTrace(trace);
190             metadataRequests.add(metadataRequest);
191         }
192 
193         List<MetadataResult> metadataResults = metadataResolver.resolveMetadata(session, metadataRequests);
194 
195         WorkspaceReader workspace = session.getWorkspaceReader();
196         if (workspace != null) {
197             List<String> versions = workspace.findVersions(request.getArtifact());
198             for (String version : versions) {
199                 versionIndex.put(version, workspace.getRepository());
200             }
201         }
202 
203         for (MetadataResult metadataResult : metadataResults) {
204             result.addException(metadataResult.getException());
205 
206             ArtifactRepository repository = metadataResult.getRequest().getRepository();
207             if (repository == null) {
208                 repository = session.getLocalRepository();
209             }
210 
211             Versioning versioning = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
212 
213             versioning = filterVersionsByRepositoryType(
214                     versioning, metadataResult.getRequest().getRepository());
215 
216             for (String version : versioning.getVersions()) {
217                 if (!versionIndex.containsKey(version)) {
218                     versionIndex.put(version, repository);
219                 }
220             }
221         }
222 
223         return versionIndex;
224     }
225 
226     private Versioning readVersions(
227             RepositorySystemSession session,
228             RequestTrace trace,
229             Metadata metadata,
230             ArtifactRepository repository,
231             VersionRangeResult result) {
232         Versioning versioning = null;
233         try {
234             if (metadata != null) {
235                 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
236                     syncContext.acquire(null, Collections.singleton(metadata));
237 
238                     if (metadata.getPath() != null && Files.exists(metadata.getPath())) {
239                         try (InputStream in = Files.newInputStream(metadata.getPath())) {
240                             versioning = new Versioning(
241                                     new MetadataStaxReader().read(in, false).getVersioning());
242                         }
243                     }
244                 }
245             }
246         } catch (Exception e) {
247             invalidMetadata(session, trace, metadata, repository, e);
248             result.addException(e);
249         }
250 
251         return (versioning != null) ? versioning : new Versioning();
252     }
253 
254     private Versioning filterVersionsByRepositoryType(Versioning versioning, RemoteRepository remoteRepository) {
255         if (remoteRepository == null) {
256             return versioning;
257         }
258 
259         Versioning filteredVersions = versioning.clone();
260 
261         for (String version : versioning.getVersions()) {
262             if (!remoteRepository.getPolicy(ArtifactUtils.isSnapshot(version)).isEnabled()) {
263                 filteredVersions.removeVersion(version);
264             }
265         }
266 
267         return filteredVersions;
268     }
269 
270     private void invalidMetadata(
271             RepositorySystemSession session,
272             RequestTrace trace,
273             Metadata metadata,
274             ArtifactRepository repository,
275             Exception exception) {
276         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
277         event.setTrace(trace);
278         event.setMetadata(metadata);
279         event.setException(exception);
280         event.setRepository(repository);
281 
282         repositoryEventDispatcher.dispatch(event.build());
283     }
284 }