1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.IOException;
27 import java.io.InputStream;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Objects;
35
36 import org.apache.maven.artifact.ArtifactUtils;
37 import org.apache.maven.artifact.repository.metadata.Versioning;
38 import org.apache.maven.repository.internal.metadata.ValidatingMetadataXpp3Reader;
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.util.version.GenericVersionScheme;
60 import org.eclipse.aether.version.InvalidVersionSpecificationException;
61 import org.eclipse.aether.version.Version;
62 import org.eclipse.aether.version.VersionConstraint;
63 import org.eclipse.aether.version.VersionRange;
64 import org.eclipse.aether.version.VersionScheme;
65
66
67
68
69 @Named
70 @Singleton
71 public class DefaultVersionRangeResolver implements VersionRangeResolver {
72
73
74
75
76
77
78
79
80
81 public static final String MAVEN_VERSION_RANGE_RESOLUTION_NATURE = "maven.versionRangeResolutionNature";
82
83 private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
84
85 private MetadataResolver metadataResolver;
86
87 private SyncContextFactory syncContextFactory;
88
89 private RepositoryEventDispatcher repositoryEventDispatcher;
90
91 @Deprecated
92 public DefaultVersionRangeResolver() {
93
94 }
95
96 @Inject
97 public DefaultVersionRangeResolver(
98 MetadataResolver metadataResolver,
99 SyncContextFactory syncContextFactory,
100 RepositoryEventDispatcher repositoryEventDispatcher) {
101 setMetadataResolver(metadataResolver);
102 setSyncContextFactory(syncContextFactory);
103 setRepositoryEventDispatcher(repositoryEventDispatcher);
104 }
105
106 public DefaultVersionRangeResolver setMetadataResolver(MetadataResolver metadataResolver) {
107 this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
108 return this;
109 }
110
111 public DefaultVersionRangeResolver setSyncContextFactory(SyncContextFactory syncContextFactory) {
112 this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
113 return this;
114 }
115
116 public DefaultVersionRangeResolver setRepositoryEventDispatcher(
117 RepositoryEventDispatcher repositoryEventDispatcher) {
118 this.repositoryEventDispatcher =
119 Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
120 return this;
121 }
122
123 @Override
124 public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request)
125 throws VersionRangeResolutionException {
126 VersionRangeResult result = new VersionRangeResult(request);
127
128 VersionScheme versionScheme = new GenericVersionScheme();
129
130 VersionConstraint versionConstraint;
131 try {
132 versionConstraint =
133 versionScheme.parseVersionConstraint(request.getArtifact().getVersion());
134 } catch (InvalidVersionSpecificationException e) {
135 result.addException(e);
136 throw new VersionRangeResolutionException(result);
137 }
138
139 result.setVersionConstraint(versionConstraint);
140
141 if (versionConstraint.getRange() == null) {
142 result.addVersion(versionConstraint.getVersion());
143 } else {
144 VersionRange.Bound lowerBound = versionConstraint.getRange().getLowerBound();
145 VersionRange.Bound upperBound = versionConstraint.getRange().getUpperBound();
146 if (lowerBound != null && lowerBound.equals(upperBound)) {
147 result.addVersion(lowerBound.getVersion());
148 } else {
149 Metadata.Nature wantedNature;
150 String natureString = ConfigUtils.getString(
151 session, request.getNature().name(), MAVEN_VERSION_RANGE_RESOLUTION_NATURE);
152 if ("auto".equals(natureString)) {
153 org.eclipse.aether.artifact.Artifact lowerArtifact = lowerBound != null
154 ? request.getArtifact()
155 .setVersion(lowerBound.getVersion().toString())
156 : null;
157 org.eclipse.aether.artifact.Artifact upperArtifact = upperBound != null
158 ? request.getArtifact()
159 .setVersion(upperBound.getVersion().toString())
160 : null;
161
162 if (lowerArtifact != null && lowerArtifact.isSnapshot()
163 || upperArtifact != null && upperArtifact.isSnapshot()) {
164 wantedNature = Metadata.Nature.RELEASE_OR_SNAPSHOT;
165 } else {
166 wantedNature = Metadata.Nature.RELEASE;
167 }
168 } else {
169 wantedNature = Metadata.Nature.valueOf(natureString.toUpperCase(Locale.ROOT));
170 }
171
172 Map<String, ArtifactRepository> versionIndex = getVersions(session, result, request, wantedNature);
173
174 List<Version> versions = new ArrayList<>();
175 for (Map.Entry<String, ArtifactRepository> v : versionIndex.entrySet()) {
176 try {
177 Version ver = versionScheme.parseVersion(v.getKey());
178 if (versionConstraint.containsVersion(ver)) {
179 versions.add(ver);
180 result.setRepository(ver, v.getValue());
181 }
182 } catch (InvalidVersionSpecificationException e) {
183 result.addException(e);
184 }
185 }
186
187 Collections.sort(versions);
188 result.setVersions(versions);
189 }
190 }
191
192 return result;
193 }
194
195 private Map<String, ArtifactRepository> getVersions(
196 RepositorySystemSession session,
197 VersionRangeResult result,
198 VersionRangeRequest request,
199 Metadata.Nature wantedNature) {
200 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
201
202 Map<String, ArtifactRepository> versionIndex = new HashMap<>();
203
204 Metadata metadata = new DefaultMetadata(
205 request.getArtifact().getGroupId(),
206 request.getArtifact().getArtifactId(),
207 MAVEN_METADATA_XML,
208 wantedNature);
209
210 List<MetadataRequest> metadataRequests =
211 new ArrayList<>(request.getRepositories().size());
212
213 metadataRequests.add(new MetadataRequest(metadata, null, request.getRequestContext()));
214
215 for (RemoteRepository repository : request.getRepositories()) {
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 List<String> versions = workspace.findVersions(request.getArtifact());
227 for (String version : versions) {
228 versionIndex.put(version, workspace.getRepository());
229 }
230 }
231
232 for (MetadataResult metadataResult : metadataResults) {
233 result.addException(metadataResult.getException());
234
235 ArtifactRepository repository = metadataResult.getRequest().getRepository();
236 if (repository == null) {
237 repository = session.getLocalRepository();
238 }
239
240 Versioning versioning = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
241
242 versioning = filterVersionsByRepositoryType(
243 versioning, metadataResult.getRequest().getRepository());
244
245 for (String version : versioning.getVersions()) {
246 if (!versionIndex.containsKey(version)) {
247 versionIndex.put(version, repository);
248 }
249 }
250 }
251
252 return versionIndex;
253 }
254
255 private Versioning readVersions(
256 RepositorySystemSession session,
257 RequestTrace trace,
258 Metadata metadata,
259 ArtifactRepository repository,
260 VersionRangeResult result) {
261 Versioning versioning = null;
262 try {
263 if (metadata != null) {
264 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
265 syncContext.acquire(null, Collections.singleton(metadata));
266
267 if (metadata.getFile() != null && metadata.getFile().exists()) {
268 try (InputStream in = new FileInputStream(metadata.getFile())) {
269 Versioning parsed = new ValidatingMetadataXpp3Reader()
270 .read(in, false)
271 .getVersioning();
272
273 validateVersioning(parsed);
274
275 versioning = parsed;
276 }
277 }
278 }
279 }
280 } catch (Exception e) {
281 invalidMetadata(session, trace, metadata, repository, e);
282 result.addException(e);
283 }
284
285 return (versioning != null) ? versioning : new Versioning();
286 }
287
288
289
290
291
292 private static void validateVersioning(Versioning versioning) throws IOException {
293 if (versioning == null) {
294 return;
295 }
296 for (String version : versioning.getVersions()) {
297 validateVersionToken(version);
298 }
299 validateVersionToken(versioning.getLatest());
300 validateVersionToken(versioning.getRelease());
301 }
302
303 private static void validateVersionToken(String value) throws IOException {
304 if (value == null || value.isEmpty()) {
305 return;
306 }
307 boolean valid = !"..".equals(value);
308 if (valid) {
309 for (int i = 0; i < value.length(); i++) {
310 char c = value.charAt(i);
311 if (c == '/' || c == '\\' || c == ':' || Character.isISOControl(c)) {
312 valid = false;
313 break;
314 }
315 }
316 }
317 if (!valid) {
318 throw new IOException("Metadata contains an invalid version token: '" + value + "'");
319 }
320 }
321
322 private Versioning filterVersionsByRepositoryType(Versioning versioning, RemoteRepository remoteRepository) {
323 if (remoteRepository == null) {
324 return versioning;
325 }
326
327 Versioning filteredVersions = versioning.clone();
328
329 for (String version : versioning.getVersions()) {
330 if (!remoteRepository.getPolicy(ArtifactUtils.isSnapshot(version)).isEnabled()) {
331 filteredVersions.removeVersion(version);
332 }
333 }
334
335 return filteredVersions;
336 }
337
338 private void invalidMetadata(
339 RepositorySystemSession session,
340 RequestTrace trace,
341 Metadata metadata,
342 ArtifactRepository repository,
343 Exception exception) {
344 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
345 event.setTrace(trace);
346 event.setMetadata(metadata);
347 event.setException(exception);
348 event.setRepository(repository);
349
350 repositoryEventDispatcher.dispatch(event.build());
351 }
352 }