View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.internal.impl.resolver;
20  
21  import java.io.InputStream;
22  import java.nio.file.Files;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Objects;
29  
30  import org.apache.maven.api.di.Inject;
31  import org.apache.maven.api.di.Named;
32  import org.apache.maven.api.di.Singleton;
33  import org.apache.maven.api.metadata.Versioning;
34  import org.apache.maven.internal.impl.DefaultModelVersionParser;
35  import org.apache.maven.metadata.v4.MetadataStaxReader;
36  import org.eclipse.aether.RepositoryEvent;
37  import org.eclipse.aether.RepositoryEvent.EventType;
38  import org.eclipse.aether.RepositorySystemSession;
39  import org.eclipse.aether.RequestTrace;
40  import org.eclipse.aether.SyncContext;
41  import org.eclipse.aether.impl.MetadataResolver;
42  import org.eclipse.aether.impl.RepositoryEventDispatcher;
43  import org.eclipse.aether.impl.VersionRangeResolver;
44  import org.eclipse.aether.metadata.DefaultMetadata;
45  import org.eclipse.aether.metadata.Metadata;
46  import org.eclipse.aether.repository.ArtifactRepository;
47  import org.eclipse.aether.repository.RemoteRepository;
48  import org.eclipse.aether.repository.WorkspaceReader;
49  import org.eclipse.aether.resolution.MetadataRequest;
50  import org.eclipse.aether.resolution.MetadataResult;
51  import org.eclipse.aether.resolution.VersionRangeRequest;
52  import org.eclipse.aether.resolution.VersionRangeResolutionException;
53  import org.eclipse.aether.resolution.VersionRangeResult;
54  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
55  import org.eclipse.aether.version.InvalidVersionSpecificationException;
56  import org.eclipse.aether.version.Version;
57  import org.eclipse.aether.version.VersionConstraint;
58  import org.eclipse.aether.version.VersionRange;
59  import org.eclipse.aether.version.VersionScheme;
60  
61  /**
62   */
63  @Named
64  @Singleton
65  public class DefaultVersionRangeResolver implements VersionRangeResolver {
66  
67      private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
68  
69      private final MetadataResolver metadataResolver;
70      private final SyncContextFactory syncContextFactory;
71      private final RepositoryEventDispatcher repositoryEventDispatcher;
72      private final VersionScheme versionScheme;
73  
74      @Inject
75      public DefaultVersionRangeResolver(
76              MetadataResolver metadataResolver,
77              SyncContextFactory syncContextFactory,
78              RepositoryEventDispatcher repositoryEventDispatcher,
79              VersionScheme versionScheme) {
80          this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
81          this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
82          this.repositoryEventDispatcher =
83                  Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
84          this.versionScheme = Objects.requireNonNull(versionScheme, "versionScheme cannot be null");
85      }
86  
87      @Override
88      public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request)
89              throws VersionRangeResolutionException {
90          VersionRangeResult result = new VersionRangeResult(request);
91  
92          VersionConstraint versionConstraint;
93          try {
94              versionConstraint =
95                      versionScheme.parseVersionConstraint(request.getArtifact().getVersion());
96          } catch (InvalidVersionSpecificationException e) {
97              result.addException(e);
98              throw new VersionRangeResolutionException(result);
99          }
100 
101         result.setVersionConstraint(versionConstraint);
102 
103         if (versionConstraint.getRange() == null) {
104             result.addVersion(versionConstraint.getVersion());
105         } else {
106             VersionRange.Bound lowerBound = versionConstraint.getRange().getLowerBound();
107             if (lowerBound != null
108                     && lowerBound.equals(versionConstraint.getRange().getUpperBound())) {
109                 result.addVersion(lowerBound.getVersion());
110             } else {
111                 Map<String, ArtifactRepository> versionIndex = getVersions(session, result, request);
112 
113                 List<Version> versions = new ArrayList<>();
114                 for (Map.Entry<String, ArtifactRepository> v : versionIndex.entrySet()) {
115                     try {
116                         Version ver = versionScheme.parseVersion(v.getKey());
117                         if (versionConstraint.containsVersion(ver)) {
118                             versions.add(ver);
119                             result.setRepository(ver, v.getValue());
120                         }
121                     } catch (InvalidVersionSpecificationException e) {
122                         result.addException(e);
123                     }
124                 }
125 
126                 Collections.sort(versions);
127                 result.setVersions(versions);
128             }
129         }
130 
131         return result;
132     }
133 
134     private Map<String, ArtifactRepository> getVersions(
135             RepositorySystemSession session, VersionRangeResult result, VersionRangeRequest request) {
136         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
137 
138         Map<String, ArtifactRepository> versionIndex = new HashMap<>();
139 
140         Metadata metadata = new DefaultMetadata(
141                 request.getArtifact().getGroupId(),
142                 request.getArtifact().getArtifactId(),
143                 MAVEN_METADATA_XML,
144                 Metadata.Nature.RELEASE_OR_SNAPSHOT);
145 
146         List<MetadataRequest> metadataRequests =
147                 new ArrayList<>(request.getRepositories().size());
148 
149         metadataRequests.add(new MetadataRequest(metadata, null, request.getRequestContext()));
150 
151         for (RemoteRepository repository : request.getRepositories()) {
152             MetadataRequest metadataRequest = new MetadataRequest(metadata, repository, request.getRequestContext());
153             metadataRequest.setDeleteLocalCopyIfMissing(true);
154             metadataRequest.setTrace(trace);
155             metadataRequests.add(metadataRequest);
156         }
157 
158         List<MetadataResult> metadataResults = metadataResolver.resolveMetadata(session, metadataRequests);
159 
160         WorkspaceReader workspace = session.getWorkspaceReader();
161         if (workspace != null) {
162             List<String> versions = workspace.findVersions(request.getArtifact());
163             for (String version : versions) {
164                 versionIndex.put(version, workspace.getRepository());
165             }
166         }
167 
168         for (MetadataResult metadataResult : metadataResults) {
169             result.addException(metadataResult.getException());
170 
171             ArtifactRepository repository = metadataResult.getRequest().getRepository();
172             if (repository == null) {
173                 repository = session.getLocalRepository();
174             }
175 
176             Versioning versioning = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
177 
178             versioning = filterVersionsByRepositoryType(
179                     versioning, metadataResult.getRequest().getRepository());
180 
181             for (String version : versioning.getVersions()) {
182                 if (!versionIndex.containsKey(version)) {
183                     versionIndex.put(version, repository);
184                 }
185             }
186         }
187 
188         return versionIndex;
189     }
190 
191     private Versioning readVersions(
192             RepositorySystemSession session,
193             RequestTrace trace,
194             Metadata metadata,
195             ArtifactRepository repository,
196             VersionRangeResult result) {
197         Versioning versioning = null;
198         try {
199             if (metadata != null) {
200                 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
201                     syncContext.acquire(null, Collections.singleton(metadata));
202 
203                     if (metadata.getPath() != null && Files.exists(metadata.getPath())) {
204                         try (InputStream in = Files.newInputStream(metadata.getPath())) {
205                             versioning =
206                                     new MetadataStaxReader().read(in, false).getVersioning();
207                         }
208                     }
209                 }
210             }
211         } catch (Exception e) {
212             invalidMetadata(session, trace, metadata, repository, e);
213             result.addException(e);
214         }
215 
216         return (versioning != null) ? versioning : Versioning.newInstance();
217     }
218 
219     private Versioning filterVersionsByRepositoryType(Versioning versioning, RemoteRepository remoteRepository) {
220         if (remoteRepository == null) {
221             return versioning;
222         }
223         return versioning.withVersions(versioning.getVersions().stream()
224                 .filter(version -> remoteRepository
225                         .getPolicy(DefaultModelVersionParser.checkSnapshot(version))
226                         .isEnabled())
227                 .toList());
228     }
229 
230     private void invalidMetadata(
231             RepositorySystemSession session,
232             RequestTrace trace,
233             Metadata metadata,
234             ArtifactRepository repository,
235             Exception exception) {
236         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
237         event.setTrace(trace);
238         event.setMetadata(metadata);
239         event.setException(exception);
240         event.setRepository(repository);
241 
242         repositoryEventDispatcher.dispatch(event.build());
243     }
244 }