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 java.io.FileInputStream;
22  import java.io.InputStream;
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  import javax.inject.Inject;
30  import javax.inject.Named;
31  import javax.inject.Singleton;
32  import org.apache.maven.artifact.ArtifactUtils;
33  import org.apache.maven.artifact.repository.metadata.Versioning;
34  import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
35  import org.eclipse.aether.RepositoryEvent;
36  import org.eclipse.aether.RepositoryEvent.EventType;
37  import org.eclipse.aether.RepositorySystemSession;
38  import org.eclipse.aether.RequestTrace;
39  import org.eclipse.aether.SyncContext;
40  import org.eclipse.aether.impl.MetadataResolver;
41  import org.eclipse.aether.impl.RepositoryEventDispatcher;
42  import org.eclipse.aether.impl.VersionRangeResolver;
43  import org.eclipse.aether.metadata.DefaultMetadata;
44  import org.eclipse.aether.metadata.Metadata;
45  import org.eclipse.aether.repository.ArtifactRepository;
46  import org.eclipse.aether.repository.RemoteRepository;
47  import org.eclipse.aether.repository.WorkspaceReader;
48  import org.eclipse.aether.resolution.MetadataRequest;
49  import org.eclipse.aether.resolution.MetadataResult;
50  import org.eclipse.aether.resolution.VersionRangeRequest;
51  import org.eclipse.aether.resolution.VersionRangeResolutionException;
52  import org.eclipse.aether.resolution.VersionRangeResult;
53  import org.eclipse.aether.spi.synccontext.SyncContextFactory;
54  import org.eclipse.aether.version.InvalidVersionSpecificationException;
55  import org.eclipse.aether.version.Version;
56  import org.eclipse.aether.version.VersionConstraint;
57  import org.eclipse.aether.version.VersionRange;
58  import org.eclipse.aether.version.VersionScheme;
59  
60  /**
61   * @author Benjamin Bentmann
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      public VersionRangeResult resolveVersionRange(RepositorySystemSession session, VersionRangeRequest request)
88              throws VersionRangeResolutionException {
89          VersionRangeResult result = new VersionRangeResult(request);
90  
91          VersionConstraint versionConstraint;
92          try {
93              versionConstraint =
94                      versionScheme.parseVersionConstraint(request.getArtifact().getVersion());
95          } catch (InvalidVersionSpecificationException e) {
96              result.addException(e);
97              throw new VersionRangeResolutionException(result);
98          }
99  
100         result.setVersionConstraint(versionConstraint);
101 
102         if (versionConstraint.getRange() == null) {
103             result.addVersion(versionConstraint.getVersion());
104         } else {
105             VersionRange.Bound lowerBound = versionConstraint.getRange().getLowerBound();
106             if (lowerBound != null
107                     && lowerBound.equals(versionConstraint.getRange().getUpperBound())) {
108                 result.addVersion(lowerBound.getVersion());
109             } else {
110                 Map<String, ArtifactRepository> versionIndex = getVersions(session, result, request);
111 
112                 List<Version> versions = new ArrayList<>();
113                 for (Map.Entry<String, ArtifactRepository> v : versionIndex.entrySet()) {
114                     try {
115                         Version ver = versionScheme.parseVersion(v.getKey());
116                         if (versionConstraint.containsVersion(ver)) {
117                             versions.add(ver);
118                             result.setRepository(ver, v.getValue());
119                         }
120                     } catch (InvalidVersionSpecificationException e) {
121                         result.addException(e);
122                     }
123                 }
124 
125                 Collections.sort(versions);
126                 result.setVersions(versions);
127             }
128         }
129 
130         return result;
131     }
132 
133     private Map<String, ArtifactRepository> getVersions(
134             RepositorySystemSession session, VersionRangeResult result, VersionRangeRequest request) {
135         RequestTrace trace = RequestTrace.newChild(request.getTrace(), request);
136 
137         Map<String, ArtifactRepository> versionIndex = new HashMap<>();
138 
139         Metadata metadata = new DefaultMetadata(
140                 request.getArtifact().getGroupId(),
141                 request.getArtifact().getArtifactId(),
142                 MAVEN_METADATA_XML,
143                 Metadata.Nature.RELEASE_OR_SNAPSHOT);
144 
145         List<MetadataRequest> metadataRequests =
146                 new ArrayList<>(request.getRepositories().size());
147 
148         metadataRequests.add(new MetadataRequest(metadata, null, request.getRequestContext()));
149 
150         for (RemoteRepository repository : request.getRepositories()) {
151             MetadataRequest metadataRequest = new MetadataRequest(metadata, repository, request.getRequestContext());
152             metadataRequest.setDeleteLocalCopyIfMissing(true);
153             metadataRequest.setTrace(trace);
154             metadataRequests.add(metadataRequest);
155         }
156 
157         List<MetadataResult> metadataResults = metadataResolver.resolveMetadata(session, metadataRequests);
158 
159         WorkspaceReader workspace = session.getWorkspaceReader();
160         if (workspace != null) {
161             List<String> versions = workspace.findVersions(request.getArtifact());
162             for (String version : versions) {
163                 versionIndex.put(version, workspace.getRepository());
164             }
165         }
166 
167         for (MetadataResult metadataResult : metadataResults) {
168             result.addException(metadataResult.getException());
169 
170             ArtifactRepository repository = metadataResult.getRequest().getRepository();
171             if (repository == null) {
172                 repository = session.getLocalRepository();
173             }
174 
175             Versioning versioning = readVersions(session, trace, metadataResult.getMetadata(), repository, result);
176 
177             versioning = filterVersionsByRepositoryType(
178                     versioning, metadataResult.getRequest().getRepository());
179 
180             for (String version : versioning.getVersions()) {
181                 if (!versionIndex.containsKey(version)) {
182                     versionIndex.put(version, repository);
183                 }
184             }
185         }
186 
187         return versionIndex;
188     }
189 
190     private Versioning readVersions(
191             RepositorySystemSession session,
192             RequestTrace trace,
193             Metadata metadata,
194             ArtifactRepository repository,
195             VersionRangeResult result) {
196         Versioning versioning = null;
197         try {
198             if (metadata != null) {
199                 try (SyncContext syncContext = syncContextFactory.newInstance(session, true)) {
200                     syncContext.acquire(null, Collections.singleton(metadata));
201 
202                     if (metadata.getFile() != null && metadata.getFile().exists()) {
203                         try (InputStream in = new FileInputStream(metadata.getFile())) {
204                             versioning =
205                                     new MetadataXpp3Reader().read(in, false).getVersioning();
206                         }
207                     }
208                 }
209             }
210         } catch (Exception e) {
211             invalidMetadata(session, trace, metadata, repository, e);
212             result.addException(e);
213         }
214 
215         return (versioning != null) ? versioning : new Versioning();
216     }
217 
218     private Versioning filterVersionsByRepositoryType(Versioning versioning, RemoteRepository remoteRepository) {
219         if (remoteRepository == null) {
220             return versioning;
221         }
222 
223         Versioning filteredVersions = versioning.clone();
224 
225         for (String version : versioning.getVersions()) {
226             if (!remoteRepository.getPolicy(ArtifactUtils.isSnapshot(version)).isEnabled()) {
227                 filteredVersions.removeVersion(version);
228             }
229         }
230 
231         return filteredVersions;
232     }
233 
234     private void invalidMetadata(
235             RepositorySystemSession session,
236             RequestTrace trace,
237             Metadata metadata,
238             ArtifactRepository repository,
239             Exception exception) {
240         RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
241         event.setTrace(trace);
242         event.setMetadata(metadata);
243         event.setException(exception);
244         event.setRepository(repository);
245 
246         repositoryEventDispatcher.dispatch(event.build());
247     }
248 }