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 }