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.repository.internal;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.FileInputStream;
26  import java.io.InputStream;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Objects;
34  
35  import org.apache.maven.artifact.ArtifactUtils;
36  import org.apache.maven.artifact.repository.metadata.Versioning;
37  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
38  import org.eclipse.aether.RepositoryEvent;
39  import org.eclipse.aether.RepositoryEvent.EventType;
40  import org.eclipse.aether.RepositorySystemSession;
41  import org.eclipse.aether.RequestTrace;
42  import org.eclipse.aether.SyncContext;
43  import org.eclipse.aether.impl.MetadataResolver;
44  import org.eclipse.aether.impl.RepositoryEventDispatcher;
45  import org.eclipse.aether.impl.VersionRangeResolver;
46  import org.eclipse.aether.metadata.DefaultMetadata;
47  import org.eclipse.aether.metadata.Metadata;
48  import org.eclipse.aether.repository.ArtifactRepository;
49  import org.eclipse.aether.repository.RemoteRepository;
50  import org.eclipse.aether.repository.WorkspaceReader;
51  import org.eclipse.aether.resolution.MetadataRequest;
52  import org.eclipse.aether.resolution.MetadataResult;
53  import org.eclipse.aether.resolution.VersionRangeRequest;
54  import org.eclipse.aether.resolution.VersionRangeResolutionException;
55  import org.eclipse.aether.resolution.VersionRangeResult;
56  import org.eclipse.aether.spi.locator.Service;
57  import org.eclipse.aether.spi.locator.ServiceLocator;
58  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
59  import org.eclipse.aether.util.ConfigUtils;
60  import org.eclipse.aether.util.version.GenericVersionScheme;
61  import org.eclipse.aether.version.InvalidVersionSpecificationException;
62  import org.eclipse.aether.version.Version;
63  import org.eclipse.aether.version.VersionConstraint;
64  import org.eclipse.aether.version.VersionRange;
65  import org.eclipse.aether.version.VersionScheme;
66  
67  /**
68   * @author Benjamin Bentmann
69   */
70  @Named
71  @Singleton
72  public class DefaultVersionRangeResolver implements VersionRangeResolver, Service {
73      /**
74       * Configuration property for version range resolution used metadata {@link Metadata.Nature}.
75       * It may contain string names of {@link Metadata.Nature} enum values, or string value {@code "auto"}
76       * to decide based on range: if any of the boundary versions is snapshot, {@link Metadata.Nature#RELEASE_OR_SNAPSHOT}
77       * will be used, otherwise {@link Metadata.Nature#RELEASE}.
78       * Default (when unset) is existing Maven 3 behaviour, using {@link Metadata.Nature#RELEASE_OR_SNAPSHOT}.
79       *
80       * @since 3.9.11
81       */
82      public static final String MAVEN_VERSION_RANGE_RESOLUTION_NATURE = "maven.versionRangeResolutionNature";
83  
84      private static final String MAVEN_METADATA_XML = "maven-metadata.xml";
85  
86      private MetadataResolver metadataResolver;
87  
88      private SyncContextFactory syncContextFactory;
89  
90      private RepositoryEventDispatcher repositoryEventDispatcher;
91  
92      @Deprecated
93      public DefaultVersionRangeResolver() {
94          // enable default constructor
95      }
96  
97      @Inject
98      public DefaultVersionRangeResolver(
99              MetadataResolver metadataResolver,
100             SyncContextFactory syncContextFactory,
101             RepositoryEventDispatcher repositoryEventDispatcher) {
102         setMetadataResolver(metadataResolver);
103         setSyncContextFactory(syncContextFactory);
104         setRepositoryEventDispatcher(repositoryEventDispatcher);
105     }
106 
107     @Deprecated
108     public void initService(ServiceLocator locator) {
109         setMetadataResolver(locator.getService(MetadataResolver.class));
110         setSyncContextFactory(locator.getService(SyncContextFactory.class));
111         setRepositoryEventDispatcher(locator.getService(RepositoryEventDispatcher.class));
112     }
113 
114     public DefaultVersionRangeResolver setMetadataResolver(MetadataResolver metadataResolver) {
115         this.metadataResolver = Objects.requireNonNull(metadataResolver, "metadataResolver cannot be null");
116         return this;
117     }
118 
119     public DefaultVersionRangeResolver setSyncContextFactory(SyncContextFactory syncContextFactory) {
120         this.syncContextFactory = Objects.requireNonNull(syncContextFactory, "syncContextFactory cannot be null");
121         return this;
122     }
123 
124     public DefaultVersionRangeResolver setRepositoryEventDispatcher(
125             RepositoryEventDispatcher repositoryEventDispatcher) {
126         this.repositoryEventDispatcher =
127                 Objects.requireNonNull(repositoryEventDispatcher, "repositoryEventDispatcher cannot be null");
128         return this;
129     }
130 
131     @Override
132     public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request)
133             throws VersionRangeResolutionException {
134         VersionRangeResult result = new VersionRangeResult(request);
135 
136         VersionScheme versionScheme = new GenericVersionScheme();
137 
138         VersionConstraint versionConstraint;
139         try {
140             versionConstraint =
141                     versionScheme.parseVersionConstraint(request.getArtifact().getVersion());
142         } catch (InvalidVersionSpecificationException e) {
143             result.addException(e);
144             throw new VersionRangeResolutionException(result);
145         }
146 
147         result.setVersionConstraint(versionConstraint);
148 
149         if (versionConstraint.getRange() == null) {
150             result.addVersion(versionConstraint.getVersion());
151         } else {
152             VersionRange.Bound lowerBound = versionConstraint.getRange().getLowerBound();
153             VersionRange.Bound upperBound = versionConstraint.getRange().getUpperBound();
154             if (lowerBound != null && lowerBound.equals(upperBound)) {
155                 result.addVersion(lowerBound.getVersion());
156             } else {
157                 Metadata.Nature wantedNature;
158                 String natureString = ConfigUtils.getString(
159                         session, Metadata.Nature.RELEASE_OR_SNAPSHOT.name(), MAVEN_VERSION_RANGE_RESOLUTION_NATURE);
160                 if ("auto".equals(natureString)) {
161                     org.eclipse.aether.artifact.Artifact lowerArtifact = lowerBound != null
162                             ? request.getArtifact()
163                                     .setVersion(lowerBound.getVersion().toString())
164                             : null;
165                     org.eclipse.aether.artifact.Artifact upperArtifact = upperBound != null
166                             ? request.getArtifact()
167                                     .setVersion(upperBound.getVersion().toString())
168                             : null;
169 
170                     if (lowerArtifact != null && lowerArtifact.isSnapshot()
171                             || upperArtifact != null && upperArtifact.isSnapshot()) {
172                         wantedNature = Metadata.Nature.RELEASE_OR_SNAPSHOT;
173                     } else {
174                         wantedNature = Metadata.Nature.RELEASE;
175                     }
176                 } else {
177                     wantedNature = Metadata.Nature.valueOf(natureString.toUpperCase(Locale.ROOT));
178                 }
179 
180                 Map<String, ArtifactRepository> versionIndex = getVersions(session, result, request, wantedNature);
181 
182                 List<Version> versions = new ArrayList<>();
183                 for (Map.Entry<String, ArtifactRepository> v : versionIndex.entrySet()) {
184                     try {
185                         Version ver = versionScheme.parseVersion(v.getKey());
186                         if (versionConstraint.containsVersion(ver)) {
187                             versions.add(ver);
188                             result.setRepository(ver, v.getValue());
189                         }
190                     } catch (InvalidVersionSpecificationException e) {
191                         result.addException(e);
192                     }
193                 }
194 
195                 Collections.sort(versions);
196                 result.setVersions(versions);
197             }
198         }
199 
200         return result;
201     }
202 
203     private Map<String, ArtifactRepository> getVersions(
204             RepositorySystemSession session,
205             VersionRangeResult result,
206             VersionRangeRequest request,
207             Metadata.Nature wantedNature) {
208         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
209 
210         Map<String, ArtifactRepository> versionIndex = new HashMap<>();
211 
212         Metadata metadata = new DefaultMetadata(
213                 request.getArtifact().getGroupId(),
214                 request.getArtifact().getArtifactId(),
215                 MAVEN_METADATA_XML,
216                 wantedNature);
217 
218         List<MetadataRequest> metadataRequests =
219                 new ArrayList<>(request.getRepositories().size());
220 
221         metadataRequests.add(new MetadataRequest(metadata, null, request.getRequestContext()));
222 
223         for (RemoteRepository repository : request.getRepositories()) {
224             MetadataRequest metadataRequest = new MetadataRequest(metadata, repository, request.getRequestContext());
225             metadataRequest.setDeleteLocalCopyIfMissing(true);
226             metadataRequest.setTrace(trace);
227             metadataRequests.add(metadataRequest);
228         }
229 
230         List<MetadataResult> metadataResults = metadataResolver.resolveMetadata(session, metadataRequests);
231 
232         WorkspaceReader workspace = session.getWorkspaceReader();
233         if (workspace != null) {
234             List<String> versions = workspace.findVersions(request.getArtifact());
235             for (String version : versions) {
236                 versionIndex.put(version, workspace.getRepository());
237             }
238         }
239 
240         for (MetadataResult metadataResult : metadataResults) {
241             result.addException(metadataResult.getException());
242 
243             ArtifactRepository repository = metadataResult.getRequest().getRepository();
244             if (repository == null) {
245                 repository = session.getLocalRepository();
246             }
247 
248             Versioning versioning = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
249 
250             versioning = filterVersionsByRepositoryType(
251                     versioning, metadataResult.getRequest().getRepository());
252 
253             for (String version : versioning.getVersions()) {
254                 if (!versionIndex.containsKey(version)) {
255                     versionIndex.put(version, repository);
256                 }
257             }
258         }
259 
260         return versionIndex;
261     }
262 
263     private Versioning readVersions(
264             RepositorySystemSession session,
265             RequestTrace trace,
266             Metadata metadata,
267             ArtifactRepository repository,
268             VersionRangeResult result) {
269         Versioning versioning = null;
270         try {
271             if (metadata != null) {
272                 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
273                     syncContext.acquire(null, Collections.singleton(metadata));
274 
275                     if (metadata.getFile() != null && metadata.getFile().exists()) {
276                         try (InputStream in = new FileInputStream(metadata.getFile())) {
277                             versioning =
278                                     new MetadataXpp3Reader().read(in, false).getVersioning();
279                         }
280                     }
281                 }
282             }
283         } catch (Exception e) {
284             invalidMetadata(session, trace, metadata, repository, e);
285             result.addException(e);
286         }
287 
288         return (versioning != null) ? versioning : new Versioning();
289     }
290 
291     private Versioning filterVersionsByRepositoryType(Versioning versioning, RemoteRepository remoteRepository) {
292         if (remoteRepository == null) {
293             return versioning;
294         }
295 
296         Versioning filteredVersions = versioning.clone();
297 
298         for (String version : versioning.getVersions()) {
299             if (!remoteRepository.getPolicy(ArtifactUtils.isSnapshot(version)).isEnabled()) {
300                 filteredVersions.removeVersion(version);
301             }
302         }
303 
304         return filteredVersions;
305     }
306 
307     private void invalidMetadata(
308             RepositorySystemSession session,
309             RequestTrace trace,
310             Metadata metadata,
311             ArtifactRepository repository,
312             Exception exception) {
313         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
314         event.setTrace(trace);
315         event.setMetadata(metadata);
316         event.setException(exception);
317         event.setRepository(repository);
318 
319         repositoryEventDispatcher.dispatch(event.build());
320     }
321 }