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.IOException;
27  import java.io.InputStream;
28  import java.nio.file.Files;
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.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  @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      @Override
96      public VersionResult resolveVersion(RepositorySystemSession session, VersionRequest request)
97              throws VersionResolutionException {
98          RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
99  
100         Artifact artifact = request.getArtifact();
101 
102         String version = artifact.getVersion();
103 
104         VersionResult result = new VersionResult(request);
105 
106         Key cacheKey = null;
107         RepositoryCache cache = session.getCache();
108         if (cache != null && !ConfigUtils.getBoolean(session, false, "aether.versionResolver.noCache")) {
109             cacheKey = new Key(session, request);
110 
111             Object obj = cache.get(session, cacheKey);
112             if (obj instanceof Record) {
113                 Record record = (Record) obj;
114                 result.setVersion(record.version);
115                 result.setRepository(
116                         getRepository(session, request.getRepositories(), record.repoClass, record.repoId));
117                 return result;
118             }
119         }
120 
121         Metadata metadata;
122 
123         if (RELEASE.equals(version)) {
124             metadata = new DefaultMetadata(
125                     artifact.getGroupId(), artifact.getArtifactId(), MAVEN_METADATA_XML, Metadata.Nature.RELEASE);
126         } else if (LATEST.equals(version)) {
127             metadata = new DefaultMetadata(
128                     artifact.getGroupId(),
129                     artifact.getArtifactId(),
130                     MAVEN_METADATA_XML,
131                     Metadata.Nature.RELEASE_OR_SNAPSHOT);
132         } else if (version.endsWith(SNAPSHOT)) {
133             WorkspaceReader workspace = session.getWorkspaceReader();
134             if (workspace != null && workspace.findVersions(artifact).contains(version)) {
135                 metadata = null;
136                 result.setRepository(workspace.getRepository());
137             } else {
138                 metadata = new DefaultMetadata(
139                         artifact.getGroupId(),
140                         artifact.getArtifactId(),
141                         version,
142                         MAVEN_METADATA_XML,
143                         Metadata.Nature.SNAPSHOT);
144             }
145         } else {
146             metadata = null;
147         }
148 
149         if (metadata == null) {
150             result.setVersion(version);
151         } else {
152             List<MetadataRequest> metadataReqs =
153                     new ArrayList<>(request.getRepositories().size());
154 
155             metadataReqs.add(new MetadataRequest(metadata, null, request.getRequestContext()));
156 
157             for (RemoteRepository repository : request.getRepositories()) {
158                 MetadataRequest metadataRequest =
159                         new MetadataRequest(metadata, repository, request.getRequestContext());
160                 metadataRequest.setDeleteLocalCopyIfMissing(true);
161                 metadataRequest.setFavorLocalRepository(true);
162                 metadataRequest.setTrace(trace);
163                 metadataReqs.add(metadataRequest);
164             }
165 
166             List<MetadataResult> metadataResults = metadataResolver.resolveMetadata(session, metadataReqs);
167 
168             Map<String, VersionInfo> infos = new HashMap<>();
169 
170             for (MetadataResult metadataResult : metadataResults) {
171                 result.addException(metadataResult.getException());
172 
173                 ArtifactRepository repository = metadataResult.getRequest().getRepository();
174                 if (repository == null) {
175                     repository = session.getLocalRepository();
176                 }
177 
178                 Versioning v = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
179                 merge(artifact, infos, v, repository);
180             }
181 
182             if (RELEASE.equals(version)) {
183                 resolve(result, infos, RELEASE);
184             } else if (LATEST.equals(version)) {
185                 if (!resolve(result, infos, LATEST)) {
186                     resolve(result, infos, RELEASE);
187                 }
188 
189                 if (result.getVersion() != null && result.getVersion().endsWith(SNAPSHOT)) {
190                     VersionRequest subRequest = new VersionRequest();
191                     subRequest.setArtifact(artifact.setVersion(result.getVersion()));
192                     if (result.getRepository() instanceof RemoteRepository) {
193                         RemoteRepository r = (RemoteRepository) result.getRepository();
194                         subRequest.setRepositories(Collections.singletonList(r));
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.getFile() != null && metadata.getFile().exists()) {
247                         try (InputStream in =
248                                 Files.newInputStream(metadata.getFile().toPath())) {
249                             versioning = new Versioning(
250                                     new MetadataStaxReader().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 (versioning.getRelease() != null && !versioning.getRelease().isEmpty()) {
300             merge(RELEASE, infos, versioning.getLastUpdated(), versioning.getRelease(), repository);
301         }
302 
303         if (versioning.getLatest() != null && !versioning.getLatest().isEmpty()) {
304             merge(LATEST, infos, versioning.getLastUpdated(), versioning.getLatest(), repository);
305         }
306 
307         for (SnapshotVersion sv : versioning.getSnapshotVersions()) {
308             if (sv.getVersion() != null && !sv.getVersion().isEmpty()) {
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 (classifier == null ? "" : classifier.trim()) + ':' + (extension == null ? "" : extension.trim());
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 }