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