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