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 }