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