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.File;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.apache.maven.model.Dependency;
30  import org.apache.maven.model.Parent;
31  import org.apache.maven.model.Repository;
32  import org.apache.maven.model.building.FileModelSource;
33  import org.apache.maven.model.building.ModelSource;
34  import org.apache.maven.model.resolution.InvalidRepositoryException;
35  import org.apache.maven.model.resolution.ModelResolver;
36  import org.apache.maven.model.resolution.UnresolvableModelException;
37  import org.eclipse.aether.RepositorySystemSession;
38  import org.eclipse.aether.RequestTrace;
39  import org.eclipse.aether.artifact.Artifact;
40  import org.eclipse.aether.artifact.DefaultArtifact;
41  import org.eclipse.aether.impl.ArtifactResolver;
42  import org.eclipse.aether.impl.RemoteRepositoryManager;
43  import org.eclipse.aether.impl.VersionRangeResolver;
44  import org.eclipse.aether.repository.RemoteRepository;
45  import org.eclipse.aether.resolution.ArtifactRequest;
46  import org.eclipse.aether.resolution.ArtifactResolutionException;
47  import org.eclipse.aether.resolution.VersionRangeRequest;
48  import org.eclipse.aether.resolution.VersionRangeResolutionException;
49  import org.eclipse.aether.resolution.VersionRangeResult;
50  
51  /**
52   * A model resolver to assist building of dependency POMs. This resolver gives priority to those repositories that have
53   * been initially specified and repositories discovered in dependency POMs are recessively merged into the search chain.
54   *
55   * @author Benjamin Bentmann
56   * @see DefaultArtifactDescriptorReader
57   */
58  class DefaultModelResolver implements ModelResolver {
59  
60      private final RepositorySystemSession session;
61  
62      private final RequestTrace trace;
63  
64      private final String context;
65  
66      private List<RemoteRepository> repositories;
67  
68      private final List<RemoteRepository> externalRepositories;
69  
70      private final ArtifactResolver resolver;
71  
72      private final VersionRangeResolver versionRangeResolver;
73  
74      private final RemoteRepositoryManager remoteRepositoryManager;
75  
76      private final Set<String> repositoryIds;
77  
78      private final Set<String> externalRepositoryIds;
79  
80      DefaultModelResolver(
81              RepositorySystemSession session,
82              RequestTrace trace,
83              String context,
84              ArtifactResolver resolver,
85              VersionRangeResolver versionRangeResolver,
86              RemoteRepositoryManager remoteRepositoryManager,
87              List<RemoteRepository> repositories) {
88          this.session = session;
89          this.trace = trace;
90          this.context = context;
91          this.resolver = resolver;
92          this.versionRangeResolver = versionRangeResolver;
93          this.remoteRepositoryManager = remoteRepositoryManager;
94          this.repositories = repositories;
95          this.externalRepositories = Collections.unmodifiableList(new ArrayList<>(repositories));
96  
97          this.repositoryIds = new HashSet<>();
98          Set<String> externalIds = new HashSet<>();
99          for (RemoteRepository externalRepository : this.externalRepositories) {
100             externalIds.add(externalRepository.getId());
101         }
102         this.externalRepositoryIds = Collections.unmodifiableSet(externalIds);
103     }
104 
105     private DefaultModelResolver(DefaultModelResolver original) {
106         this.session = original.session;
107         this.trace = original.trace;
108         this.context = original.context;
109         this.resolver = original.resolver;
110         this.versionRangeResolver = original.versionRangeResolver;
111         this.remoteRepositoryManager = original.remoteRepositoryManager;
112         this.repositories = new ArrayList<>(original.repositories);
113         this.externalRepositories = original.externalRepositories;
114         this.repositoryIds = new HashSet<>(original.repositoryIds);
115         this.externalRepositoryIds = original.externalRepositoryIds;
116     }
117 
118     @Override
119     public void addRepository(Repository repository) throws InvalidRepositoryException {
120         addRepository(repository, false);
121     }
122 
123     @Override
124     public void addRepository(final Repository repository, boolean replace) throws InvalidRepositoryException {
125         if (session.isIgnoreArtifactDescriptorRepositories()) {
126             return;
127         }
128 
129         if (!repositoryIds.add(repository.getId())) {
130             if (!replace) {
131                 return;
132             }
133 
134             if (externalRepositoryIds.contains(repository.getId())) {
135                 // Replacement is meant to refresh a repository this model declared earlier, e.g.
136                 // once its URL has been interpolated. Repositories supplied by the request or the
137                 // session are not model-declared, so they keep precedence and are left in place.
138                 return;
139             }
140 
141             removeMatchingRepository(repositories, repository.getId());
142         }
143 
144         List<RemoteRepository> newRepositories =
145                 Collections.singletonList(ArtifactDescriptorUtils.toRemoteRepository(repository));
146 
147         this.repositories = remoteRepositoryManager.aggregateRepositories(session, repositories, newRepositories, true);
148     }
149 
150     private static void removeMatchingRepository(Iterable<RemoteRepository> repositories, final String id) {
151         Iterator<RemoteRepository> iterator = repositories.iterator();
152         while (iterator.hasNext()) {
153             RemoteRepository remoteRepository = iterator.next();
154             if (remoteRepository.getId().equals(id)) {
155                 iterator.remove();
156             }
157         }
158     }
159 
160     @Override
161     public ModelResolver newCopy() {
162         return new DefaultModelResolver(this);
163     }
164 
165     @Override
166     public ModelSource resolveModel(String groupId, String artifactId, String version)
167             throws UnresolvableModelException {
168         Artifact pomArtifact = new DefaultArtifact(groupId, artifactId, "", "pom", version);
169 
170         try {
171             ArtifactRequest request = new ArtifactRequest(pomArtifact, repositories, context);
172             request.setTrace(trace);
173             pomArtifact = resolver.resolveArtifact(session, request).getArtifact();
174         } catch (ArtifactResolutionException e) {
175             throw new UnresolvableModelException(e.getMessage(), groupId, artifactId, version, e);
176         }
177 
178         File pomFile = pomArtifact.getFile();
179 
180         return new FileModelSource(pomFile);
181     }
182 
183     @Override
184     public ModelSource resolveModel(final Parent parent) throws UnresolvableModelException {
185         try {
186             final Artifact artifact =
187                     new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion());
188 
189             final VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, repositories, context);
190             versionRangeRequest.setTrace(trace);
191 
192             final VersionRangeResult versionRangeResult =
193                     versionRangeResolver.resolveVersionRange(session, versionRangeRequest);
194 
195             if (versionRangeResult.getHighestVersion() == null) {
196                 throw new UnresolvableModelException(
197                         String.format(
198                                 "No versions matched the requested parent version range '%s'", parent.getVersion()),
199                         parent.getGroupId(),
200                         parent.getArtifactId(),
201                         parent.getVersion());
202             }
203 
204             if (versionRangeResult.getVersionConstraint() != null
205                     && versionRangeResult.getVersionConstraint().getRange() != null
206                     && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
207                 // Message below is checked for in the MNG-2199 core IT.
208                 throw new UnresolvableModelException(
209                         String.format(
210                                 "The requested parent version range '%s' does not specify an upper bound",
211                                 parent.getVersion()),
212                         parent.getGroupId(),
213                         parent.getArtifactId(),
214                         parent.getVersion());
215             }
216 
217             parent.setVersion(versionRangeResult.getHighestVersion().toString());
218 
219             return resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
220         } catch (final VersionRangeResolutionException e) {
221             throw new UnresolvableModelException(
222                     e.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), e);
223         }
224     }
225 
226     @Override
227     public ModelSource resolveModel(final Dependency dependency) throws UnresolvableModelException {
228         try {
229             final Artifact artifact = new DefaultArtifact(
230                     dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion());
231 
232             final VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, repositories, context);
233             versionRangeRequest.setTrace(trace);
234 
235             final VersionRangeResult versionRangeResult =
236                     versionRangeResolver.resolveVersionRange(session, versionRangeRequest);
237 
238             if (versionRangeResult.getHighestVersion() == null) {
239                 throw new UnresolvableModelException(
240                         String.format(
241                                 "No versions matched the requested dependency version range '%s'",
242                                 dependency.getVersion()),
243                         dependency.getGroupId(),
244                         dependency.getArtifactId(),
245                         dependency.getVersion());
246             }
247 
248             if (versionRangeResult.getVersionConstraint() != null
249                     && versionRangeResult.getVersionConstraint().getRange() != null
250                     && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
251                 // Message below is checked for in the MNG-4463 core IT.
252                 throw new UnresolvableModelException(
253                         String.format(
254                                 "The requested dependency version range '%s' does not specify an upper bound",
255                                 dependency.getVersion()),
256                         dependency.getGroupId(),
257                         dependency.getArtifactId(),
258                         dependency.getVersion());
259             }
260 
261             dependency.setVersion(versionRangeResult.getHighestVersion().toString());
262 
263             return resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
264         } catch (VersionRangeResolutionException e) {
265             throw new UnresolvableModelException(
266                     e.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), e);
267         }
268     }
269 }