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