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.IOException;
26 import java.io.InputStream;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35
36 import org.apache.maven.artifact.repository.metadata.Snapshot;
37 import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
38 import org.apache.maven.artifact.repository.metadata.Versioning;
39 import org.apache.maven.metadata.v4.MetadataStaxReader;
40 import org.eclipse.aether.RepositoryCache;
41 import org.eclipse.aether.RepositoryEvent;
42 import org.eclipse.aether.RepositoryEvent.EventType;
43 import org.eclipse.aether.RepositorySystemSession;
44 import org.eclipse.aether.RequestTrace;
45 import org.eclipse.aether.SyncContext;
46 import org.eclipse.aether.artifact.Artifact;
47 import org.eclipse.aether.impl.MetadataResolver;
48 import org.eclipse.aether.impl.RepositoryEventDispatcher;
49 import org.eclipse.aether.impl.VersionResolver;
50 import org.eclipse.aether.metadata.DefaultMetadata;
51 import org.eclipse.aether.metadata.Metadata;
52 import org.eclipse.aether.repository.ArtifactRepository;
53 import org.eclipse.aether.repository.LocalRepository;
54 import org.eclipse.aether.repository.RemoteRepository;
55 import org.eclipse.aether.repository.WorkspaceReader;
56 import org.eclipse.aether.repository.WorkspaceRepository;
57 import org.eclipse.aether.resolution.MetadataRequest;
58 import org.eclipse.aether.resolution.MetadataResult;
59 import org.eclipse.aether.resolution.VersionRequest;
60 import org.eclipse.aether.resolution.VersionResolutionException;
61 import org.eclipse.aether.resolution.VersionResult;
62 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
63 import org.eclipse.aether.util.ConfigUtils;
64
65
66
67 @Named
68 @Singleton
69 public class DefaultVersionResolver implements VersionResolver {
70
71 private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
72
73 private static final String RELEASE = "RELEASE";
74
75 private static final String LATEST = "LATEST";
76
77 private static final String SNAPSHOT = "SNAPSHOT";
78
79 private final MetadataResolver metadataResolver;
80 private final SyncContextFactory syncContextFactory;
81 private final RepositoryEventDispatcher repositoryEventDispatcher;
82
83 @Inject
84 public DefaultVersionResolver(
85 MetadataResolver metadataResolver,
86 SyncContextFactory syncContextFactory,
87 RepositoryEventDispatcher repositoryEventDispatcher) {
88 this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
89 this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
90 this.repositoryEventDispatcher =
91 Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
92 }
93
94 @SuppressWarnings("checkstyle:methodlength")
95 @Override
96 public VersionResult resolveVersion(RepositorySystemSession session, VersionRequest request)
97 throws VersionResolutionException {
98 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
99
100 Artifact artifact = request.getArtifact();
101
102 String version = artifact.getVersion();
103
104 VersionResult result = new VersionResult(request);
105
106 Key cacheKey = null;
107 RepositoryCache cache = session.getCache();
108 if (cache != null && !ConfigUtils.getBoolean(session, false, "aether.versionResolver.noCache")) {
109 cacheKey = new Key(session, request);
110
111 Object obj = cache.get(session, cacheKey);
112 if (obj instanceof Record) {
113 Record record = (Record) obj;
114 result.setVersion(record.version);
115 result.setRepository(
116 getRepository(session, request.getRepositories(), record.repoClass, record.repoId));
117 return result;
118 }
119 }
120
121 Metadata metadata;
122
123 if (RELEASE.equals(version)) {
124 metadata = new DefaultMetadata(
125 artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML, Metadata.Nature.RELEASE);
126 } else if (LATEST.equals(version)) {
127 metadata = new DefaultMetadata(
128 artifact.getGroupId(),
129 artifact.getArtifactId(),
130 MAVEN_METADATA_XML,
131 Metadata.Nature.RELEASE_OR_SNAPSHOT);
132 } else if (version.endsWith(SNAPSHOT)) {
133 WorkspaceReader workspace = session.getWorkspaceReader();
134 if (workspace != null && workspace.findVersions(artifact).contains(version)) {
135 metadata = null;
136 result.setRepository(workspace.getRepository());
137 } else {
138 metadata = new DefaultMetadata(
139 artifact.getGroupId(),
140 artifact.getArtifactId(),
141 version,
142 MAVEN_METADATA_XML,
143 Metadata.Nature.SNAPSHOT);
144 }
145 } else {
146 metadata = null;
147 }
148
149 if (metadata == null) {
150 result.setVersion(version);
151 } else {
152 List<MetadataRequest> metadataReqs =
153 new ArrayList<>(request.getRepositories().size());
154
155 metadataReqs.add(new MetadataRequest(metadata, null, request.getRequestContext()));
156
157 for (RemoteRepository repository : request.getRepositories()) {
158 MetadataRequest metadataRequest =
159 new MetadataRequest(metadata, repository, request.getRequestContext());
160 metadataRequest.setDeleteLocalCopyIfMissing(true);
161 metadataRequest.setFavorLocalRepository(true);
162 metadataRequest.setTrace(trace);
163 metadataReqs.add(metadataRequest);
164 }
165
166 List<MetadataResult> metadataResults = metadataResolver.resolveMetadata(session, metadataReqs);
167
168 Map<String, VersionInfo> infos = new HashMap<>();
169
170 for (MetadataResult metadataResult : metadataResults) {
171 result.addException(metadataResult.getException());
172
173 ArtifactRepository repository = metadataResult.getRequest().getRepository();
174 if (repository == null) {
175 repository = session.getLocalRepository();
176 }
177
178 Versioning v = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
179 merge(artifact, infos, v, repository);
180 }
181
182 if (RELEASE.equals(version)) {
183 resolve(result, infos, RELEASE);
184 } else if (LATEST.equals(version)) {
185 if (!resolve(result, infos, LATEST)) {
186 resolve(result, infos, RELEASE);
187 }
188
189 if (result.getVersion() != null && result.getVersion().endsWith(SNAPSHOT)) {
190 VersionRequest subRequest = new VersionRequest();
191 subRequest.setArtifact(artifact.setVersion(result.getVersion()));
192 if (result.getRepository() instanceof RemoteRepository) {
193 RemoteRepository r = (RemoteRepository) result.getRepository();
194 subRequest.setRepositories(Collections.singletonList(r));
195 } else {
196 subRequest.setRepositories(request.getRepositories());
197 }
198 VersionResult subResult = resolveVersion(session, subRequest);
199 result.setVersion(subResult.getVersion());
200 result.setRepository(subResult.getRepository());
201 for (Exception exception : subResult.getExceptions()) {
202 result.addException(exception);
203 }
204 }
205 } else {
206 String key = SNAPSHOT + getKey(artifact.getClassifier(), artifact.getExtension());
207 merge(infos, SNAPSHOT, key);
208 if (!resolve(result, infos, key)) {
209 result.setVersion(version);
210 }
211 }
212
213 if (result.getVersion() == null || result.getVersion().isEmpty()) {
214 throw new VersionResolutionException(result);
215 }
216 }
217
218 if (cacheKey != null && metadata != null && isSafelyCacheable(session, artifact)) {
219 cache.put(session, cacheKey, new Record(result.getVersion(), result.getRepository()));
220 }
221
222 return result;
223 }
224
225 private boolean resolve(VersionResult result, Map<String, VersionInfo> infos, String key) {
226 VersionInfo info = infos.get(key);
227 if (info != null) {
228 result.setVersion(info.version);
229 result.setRepository(info.repository);
230 }
231 return info != null;
232 }
233
234 private Versioning readVersions(
235 RepositorySystemSession session,
236 RequestTrace trace,
237 Metadata metadata,
238 ArtifactRepository repository,
239 VersionResult result) {
240 Versioning versioning = null;
241 try {
242 if (metadata != null) {
243 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
244 syncContext.acquire(null, Collections.singleton(metadata));
245
246 if (metadata.getPath() != null && Files.exists(metadata.getPath())) {
247 try (InputStream in = Files.newInputStream(metadata.getPath())) {
248 versioning = new Versioning(
249 new MetadataStaxReader().read(in, false).getVersioning());
250
251
252
253
254
255
256 if (versioning != null
257 && repository instanceof LocalRepository
258 && versioning.getSnapshot() != null
259 && versioning.getSnapshot().getBuildNumber() > 0) {
260 final Versioning repaired = new Versioning();
261 repaired.setLastUpdated(versioning.getLastUpdated());
262 repaired.setSnapshot(new Snapshot());
263 repaired.getSnapshot().setLocalCopy(true);
264 versioning = repaired;
265 throw new IOException("Snapshot information corrupted with remote repository data"
266 + ", please verify that no remote repository uses the id '"
267 + repository.getId() + "'");
268 }
269 }
270 }
271 }
272 }
273 } catch (Exception e) {
274 invalidMetadata(session, trace, metadata, repository, e);
275 result.addException(e);
276 }
277
278 return (versioning != null) ? versioning : new Versioning();
279 }
280
281 private void invalidMetadata(
282 RepositorySystemSession session,
283 RequestTrace trace,
284 Metadata metadata,
285 ArtifactRepository repository,
286 Exception exception) {
287 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
288 event.setTrace(trace);
289 event.setMetadata(metadata);
290 event.setException(exception);
291 event.setRepository(repository);
292
293 repositoryEventDispatcher.dispatch(event.build());
294 }
295
296 private void merge(
297 Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning, ArtifactRepository repository) {
298 if (versioning.getRelease() != null && !versioning.getRelease().isEmpty()) {
299 merge(RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository);
300 }
301
302 if (versioning.getLatest() != null && !versioning.getLatest().isEmpty()) {
303 merge(LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository);
304 }
305
306 for (SnapshotVersion sv : versioning.getSnapshotVersions()) {
307 if (sv.getVersion() != null && !sv.getVersion().isEmpty()) {
308 String key = getKey(sv.getClassifier(), sv.getExtension());
309 merge(SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository);
310 }
311 }
312
313 Snapshot snapshot = versioning.getSnapshot();
314 if (snapshot != null && versioning.getSnapshotVersions().isEmpty()) {
315 String version = artifact.getVersion();
316 if (snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0) {
317 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber();
318 version = version.substring(0, version.length() - SNAPSHOT.length()) + qualifier;
319 }
320 merge(SNAPSHOT, infos, versioning.getLastUpdated(), version, repository);
321 }
322 }
323
324 private void merge(
325 String key,
326 Map<String, VersionInfo> infos,
327 String timestamp,
328 String version,
329 ArtifactRepository repository) {
330 VersionInfo info = infos.get(key);
331 if (info == null) {
332 info = new VersionInfo(timestamp, version, repository);
333 infos.put(key, info);
334 } else if (info.isOutdated(timestamp)) {
335 info.version = version;
336 info.repository = repository;
337 info.timestamp = timestamp;
338 }
339 }
340
341 private void merge(Map<String, VersionInfo> infos, String srcKey, String dstKey) {
342 VersionInfo srcInfo = infos.get(srcKey);
343 VersionInfo dstInfo = infos.get(dstKey);
344
345 if (dstInfo == null
346 || (srcInfo != null
347 && dstInfo.isOutdated(srcInfo.timestamp)
348 && srcInfo.repository != dstInfo.repository)) {
349 infos.put(dstKey, srcInfo);
350 }
351 }
352
353 private String getKey(String classifier, String extension) {
354 return (classifier == null ? "" : classifier.trim()) + ':' + (extension == null ? "" : extension.trim());
355 }
356
357 private ArtifactRepository getRepository(
358 RepositorySystemSession session, List<RemoteRepository> repositories, Class<?> repoClass, String repoId) {
359 if (repoClass != null) {
360 if (WorkspaceRepository.class.isAssignableFrom(repoClass)) {
361 return session.getWorkspaceReader().getRepository();
362 } else if (LocalRepository.class.isAssignableFrom(repoClass)) {
363 return session.getLocalRepository();
364 } else {
365 for (RemoteRepository repository : repositories) {
366 if (repoId.equals(repository.getId())) {
367 return repository;
368 }
369 }
370 }
371 }
372 return null;
373 }
374
375 private boolean isSafelyCacheable(RepositorySystemSession session, Artifact artifact) {
376
377
378
379
380
381 WorkspaceReader workspace = session.getWorkspaceReader();
382 if (workspace == null) {
383 return true;
384 }
385
386 Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact(artifact);
387
388 return workspace.findArtifact(pomArtifact) == null;
389 }
390
391 private static class VersionInfo {
392
393 String timestamp;
394
395 String version;
396
397 ArtifactRepository repository;
398
399 VersionInfo(String timestamp, String version, ArtifactRepository repository) {
400 this.timestamp = (timestamp != null) ? timestamp : "";
401 this.version = version;
402 this.repository = repository;
403 }
404
405 boolean isOutdated(String timestamp) {
406 return timestamp != null && timestamp.compareTo(this.timestamp) > 0;
407 }
408 }
409
410 private static class Key {
411
412 private final String groupId;
413
414 private final String artifactId;
415
416 private final String classifier;
417
418 private final String extension;
419
420 private final String version;
421
422 private final String context;
423
424 private final Path localRepo;
425
426 private final WorkspaceRepository workspace;
427
428 private final List<RemoteRepository> repositories;
429
430 private final int hashCode;
431
432 Key(RepositorySystemSession session, VersionRequest request) {
433 Artifact artifact = request.getArtifact();
434 groupId = artifact.getGroupId();
435 artifactId = artifact.getArtifactId();
436 classifier = artifact.getClassifier();
437 extension = artifact.getExtension();
438 version = artifact.getVersion();
439 localRepo = session.getLocalRepository().getBasePath();
440 WorkspaceReader reader = session.getWorkspaceReader();
441 workspace = (reader != null) ? reader.getRepository() : null;
442 repositories = new ArrayList<>(request.getRepositories().size());
443 boolean repoMan = false;
444 for (RemoteRepository repository : request.getRepositories()) {
445 if (repository.isRepositoryManager()) {
446 repoMan = true;
447 repositories.addAll(repository.getMirroredRepositories());
448 } else {
449 repositories.add(repository);
450 }
451 }
452 context = repoMan ? request.getRequestContext() : "";
453
454 int hash = 17;
455 hash = hash * 31 + groupId.hashCode();
456 hash = hash * 31 + artifactId.hashCode();
457 hash = hash * 31 + classifier.hashCode();
458 hash = hash * 31 + extension.hashCode();
459 hash = hash * 31 + version.hashCode();
460 hash = hash * 31 + localRepo.hashCode();
461 hash = hash * 31 + repositories.hashCode();
462 hashCode = hash;
463 }
464
465 @Override
466 public boolean equals(Object obj) {
467 if (obj == this) {
468 return true;
469 } else if (obj == null || !getClass().equals(obj.getClass())) {
470 return false;
471 }
472
473 Key that = (Key) obj;
474 return artifactId.equals(that.artifactId)
475 && groupId.equals(that.groupId)
476 && classifier.equals(that.classifier)
477 && extension.equals(that.extension)
478 && version.equals(that.version)
479 && context.equals(that.context)
480 && localRepo.equals(that.localRepo)
481 && Objects.equals(workspace, that.workspace)
482 && repositories.equals(that.repositories);
483 }
484
485 @Override
486 public int hashCode() {
487 return hashCode;
488 }
489 }
490
491 private static class Record {
492 final String version;
493
494 final String repoId;
495
496 final Class<?> repoClass;
497
498 Record(String version, ArtifactRepository repository) {
499 this.version = version;
500 if (repository != null) {
501 repoId = repository.getId();
502 repoClass = repository.getClass();
503 } else {
504 repoId = null;
505 repoClass = null;
506 }
507 }
508 }
509 }