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