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 record) {
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 remoteRepository) {
194                         subRequest.setRepositories(Collections.singletonList(remoteRepository));
195                     } else {
196                         subRequest.setRepositories(request.getRepositories());
197                     }
198                     VersionResult subResult = resolveVersion(session, subRequest);
199                     result.setVersion(subResult.getVersion());
200                     result.setRepository(subResult.getRepository());
201                     for (Exception exception : subResult.getExceptions()) {
202                         result.addException(exception);
203                     }
204                 }
205             } else {
206                 String key = SNAPSHOT + getKey(artifact.getClassifier(), artifact.getExtension());
207                 merge(infos, SNAPSHOT, key);
208                 if (!resolve(result, infos, key)) {
209                     result.setVersion(version);
210                 }
211             }
212 
213             if (result.getVersion() == null || result.getVersion().isEmpty()) {
214                 throw new VersionResolutionException(result);
215             }
216         }
217 
218         if (cacheKey != null && metadata != null && isSafelyCacheable(session, artifact)) {
219             cache.put(session, cacheKey, new Record(result.getVersion(), result.getRepository()));
220         }
221 
222         return result;
223     }
224 
225     private boolean resolve(VersionResult result, Map<String, VersionInfo> infos, String key) {
226         VersionInfo info = infos.get(key);
227         if (info != null) {
228             result.setVersion(info.version);
229             result.setRepository(info.repository);
230         }
231         return info != null;
232     }
233 
234     private Versioning readVersions(
235             RepositorySystemSession session,
236             RequestTrace trace,
237             Metadata metadata,
238             ArtifactRepository repository,
239             VersionResult result) {
240         Versioning versioning = null;
241         try {
242             if (metadata != null) {
243                 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
244                     syncContext.acquire(null, Collections.singleton(metadata));
245 
246                     if (metadata.getPath() != null && Files.exists(metadata.getPath())) {
247                         try (InputStream in = Files.newInputStream(metadata.getPath())) {
248                             versioning = new Versioning(
249                                     new MetadataStaxReader().read(in, false).getVersioning());
250 
251                             
252 
253 
254 
255 
256                             if (versioning != null
257                                     && repository instanceof LocalRepository
258                                     && versioning.getSnapshot() != null
259                                     && versioning.getSnapshot().getBuildNumber() > 0) {
260                                 final Versioning repaired = new Versioning();
261                                 repaired.setLastUpdated(versioning.getLastUpdated());
262                                 repaired.setSnapshot(new Snapshot());
263                                 repaired.getSnapshot().setLocalCopy(true);
264                                 versioning = repaired;
265                                 throw new IOException("Snapshot information corrupted with remote repository data"
266                                         + ", please verify that no remote repository uses the id '"
267                                         + repository.getId() + "'");
268                             }
269                         }
270                     }
271                 }
272             }
273         } catch (Exception e) {
274             invalidMetadata(session, trace, metadata, repository, e);
275             result.addException(e);
276         }
277 
278         return (versioning != null) ? versioning : new Versioning();
279     }
280 
281     private void invalidMetadata(
282             RepositorySystemSession session,
283             RequestTrace trace,
284             Metadata metadata,
285             ArtifactRepository repository,
286             Exception exception) {
287         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
288         event.setTrace(trace);
289         event.setMetadata(metadata);
290         event.setException(exception);
291         event.setRepository(repository);
292 
293         repositoryEventDispatcher.dispatch(event.build());
294     }
295 
296     private void merge(
297             Artifact artifact, Map<String, VersionInfo> infos, Versioning versioning, ArtifactRepository repository) {
298         if (versioning.getRelease() != null && !versioning.getRelease().isEmpty()) {
299             merge(RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository);
300         }
301 
302         if (versioning.getLatest() != null && !versioning.getLatest().isEmpty()) {
303             merge(LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository);
304         }
305 
306         for (SnapshotVersion sv : versioning.getSnapshotVersions()) {
307             if (sv.getVersion() != null && !sv.getVersion().isEmpty()) {
308                 String key = getKey(sv.getClassifier(), sv.getExtension());
309                 merge(SNAPSHOT + key, infos, sv.getUpdated(), sv.getVersion(), repository);
310             }
311         }
312 
313         Snapshot snapshot = versioning.getSnapshot();
314         if (snapshot != null && versioning.getSnapshotVersions().isEmpty()) {
315             String version = artifact.getVersion();
316             if (snapshot.getTimestamp() != null && snapshot.getBuildNumber() > 0) {
317                 String qualifier = snapshot.getTimestamp() + '-' + snapshot.getBuildNumber();
318                 version = version.substring(0, version.length() - SNAPSHOT.length()) + qualifier;
319             }
320             merge(SNAPSHOT, infos, versioning.getLastUpdated(), version, repository);
321         }
322     }
323 
324     private void merge(
325             String key,
326             Map<String, VersionInfo> infos,
327             String timestamp,
328             String version,
329             ArtifactRepository repository) {
330         VersionInfo info = infos.get(key);
331         if (info == null) {
332             info = new VersionInfo(timestamp, version, repository);
333             infos.put(key, info);
334         } else if (info.isOutdated(timestamp)) {
335             info.version = version;
336             info.repository = repository;
337             info.timestamp = timestamp;
338         }
339     }
340 
341     private void merge(Map<String, VersionInfo> infos, String srcKey, String dstKey) {
342         VersionInfo srcInfo = infos.get(srcKey);
343         VersionInfo dstInfo = infos.get(dstKey);
344 
345         if (dstInfo == null
346                 || (srcInfo != null
347                         && dstInfo.isOutdated(srcInfo.timestamp)
348                         && srcInfo.repository != dstInfo.repository)) {
349             infos.put(dstKey, srcInfo);
350         }
351     }
352 
353     private String getKey(String classifier, String extension) {
354         return (classifier == null ? "" : classifier.trim()) + ':' + (extension == null ? "" : extension.trim());
355     }
356 
357     private ArtifactRepository getRepository(
358             RepositorySystemSession session, List<RemoteRepository> repositories, Class<?> repoClass, String repoId) {
359         if (repoClass != null) {
360             if (WorkspaceRepository.class.isAssignableFrom(repoClass)) {
361                 return session.getWorkspaceReader().getRepository();
362             } else if (LocalRepository.class.isAssignableFrom(repoClass)) {
363                 return session.getLocalRepository();
364             } else {
365                 for (RemoteRepository repository : repositories) {
366                     if (repoId.equals(repository.getId())) {
367                         return repository;
368                     }
369                 }
370             }
371         }
372         return null;
373     }
374 
375     private boolean isSafelyCacheable(RepositorySystemSession session, Artifact artifact) {
376         
377 
378 
379 
380 
381         WorkspaceReader workspace = session.getWorkspaceReader();
382         if (workspace == null) {
383             return true;
384         }
385 
386         Artifact pomArtifact = ArtifactDescriptorUtils.toPomArtifact(artifact);
387 
388         return workspace.findArtifact(pomArtifact) == null;
389     }
390 
391     private static class VersionInfo {
392 
393         String timestamp;
394 
395         String version;
396 
397         ArtifactRepository repository;
398 
399         VersionInfo(String timestamp, String version, ArtifactRepository repository) {
400             this.timestamp = (timestamp != null) ? timestamp : "";
401             this.version = version;
402             this.repository = repository;
403         }
404 
405         boolean isOutdated(String timestamp) {
406             return timestamp != null && timestamp.compareTo(this.timestamp) > 0;
407         }
408     }
409 
410     private static class Key {
411 
412         private final String groupId;
413 
414         private final String artifactId;
415 
416         private final String classifier;
417 
418         private final String extension;
419 
420         private final String version;
421 
422         private final String context;
423 
424         private final Path localRepo;
425 
426         private final WorkspaceRepository workspace;
427 
428         private final List<RemoteRepository> repositories;
429 
430         private final int hashCode;
431 
432         Key(RepositorySystemSession session, VersionRequest request) {
433             Artifact artifact = request.getArtifact();
434             groupId = artifact.getGroupId();
435             artifactId = artifact.getArtifactId();
436             classifier = artifact.getClassifier();
437             extension = artifact.getExtension();
438             version = artifact.getVersion();
439             localRepo = session.getLocalRepository().getBasePath();
440             WorkspaceReader reader = session.getWorkspaceReader();
441             workspace = (reader != null) ? reader.getRepository() : null;
442             repositories = new ArrayList<>(request.getRepositories().size());
443             boolean repoMan = false;
444             for (RemoteRepository repository : request.getRepositories()) {
445                 if (repository.isRepositoryManager()) {
446                     repoMan = true;
447                     repositories.addAll(repository.getMirroredRepositories());
448                 } else {
449                     repositories.add(repository);
450                 }
451             }
452             context = repoMan ? request.getRequestContext() : "";
453 
454             int hash = 17;
455             hash = hash * 31 + groupId.hashCode();
456             hash = hash * 31 + artifactId.hashCode();
457             hash = hash * 31 + classifier.hashCode();
458             hash = hash * 31 + extension.hashCode();
459             hash = hash * 31 + version.hashCode();
460             hash = hash * 31 + localRepo.hashCode();
461             hash = hash * 31 + repositories.hashCode();
462             hashCode = hash;
463         }
464 
465         @Override
466         public boolean equals(Object obj) {
467             if (obj == this) {
468                 return true;
469             } else if (obj == null || !getClass().equals(obj.getClass())) {
470                 return false;
471             }
472 
473             Key that = (Key) obj;
474             return artifactId.equals(that.artifactId)
475                     && groupId.equals(that.groupId)
476                     && classifier.equals(that.classifier)
477                     && extension.equals(that.extension)
478                     && version.equals(that.version)
479                     && context.equals(that.context)
480                     && localRepo.equals(that.localRepo)
481                     && Objects.equals(workspace, that.workspace)
482                     && repositories.equals(that.repositories);
483         }
484 
485         @Override
486         public int hashCode() {
487             return hashCode;
488         }
489     }
490 
491     private static class Record {
492         final String version;
493 
494         final String repoId;
495 
496         final Class<?> repoClass;
497 
498         Record(String version, ArtifactRepository repository) {
499             this.version = version;
500             if (repository != null) {
501                 repoId = repository.getId();
502                 repoClass = repository.getClass();
503             } else {
504                 repoId = null;
505                 repoClass = null;
506             }
507         }
508     }
509 }