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