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