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.project;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.ForkJoinPool;
30  import java.util.concurrent.ForkJoinTask;
31  import java.util.concurrent.Future;
32  import java.util.concurrent.atomic.AtomicReference;
33  
34  import org.apache.maven.api.model.Dependency;
35  import org.apache.maven.api.model.Model;
36  import org.apache.maven.api.model.Parent;
37  import org.apache.maven.api.model.Repository;
38  import org.apache.maven.internal.impl.resolver.ArtifactDescriptorUtils;
39  import org.apache.maven.model.building.ArtifactModelSource;
40  import org.apache.maven.model.building.FileModelSource;
41  import org.apache.maven.model.building.ModelSource;
42  import org.apache.maven.model.resolution.InvalidRepositoryException;
43  import org.apache.maven.model.resolution.ModelResolver;
44  import org.apache.maven.model.resolution.UnresolvableModelException;
45  import org.eclipse.aether.RepositorySystem;
46  import org.eclipse.aether.RepositorySystemSession;
47  import org.eclipse.aether.RequestTrace;
48  import org.eclipse.aether.artifact.Artifact;
49  import org.eclipse.aether.artifact.DefaultArtifact;
50  import org.eclipse.aether.impl.RemoteRepositoryManager;
51  import org.eclipse.aether.repository.RemoteRepository;
52  import org.eclipse.aether.resolution.ArtifactRequest;
53  import org.eclipse.aether.resolution.ArtifactResolutionException;
54  import org.eclipse.aether.resolution.VersionRangeRequest;
55  import org.eclipse.aether.resolution.VersionRangeResolutionException;
56  import org.eclipse.aether.resolution.VersionRangeResult;
57  
58  /**
59   * A model resolver to assist building of projects. This resolver gives priority to those repositories that have been
60   * declared in the POM.
61   *
62   */
63  public class ProjectModelResolver implements ModelResolver {
64  
65      private static final int MAX_CAP = 0x7fff;
66  
67      private final RepositorySystemSession session;
68  
69      private final RequestTrace trace;
70  
71      private final String context = "project";
72  
73      private List<RemoteRepository> repositories;
74  
75      private List<RemoteRepository> pomRepositories;
76  
77      private final List<RemoteRepository> externalRepositories;
78  
79      private final RepositorySystem resolver;
80  
81      private final RemoteRepositoryManager remoteRepositoryManager;
82  
83      private final Set<String> repositoryIds;
84  
85      private final ReactorModelPool modelPool;
86  
87      private final ProjectBuildingRequest.RepositoryMerging repositoryMerging;
88  
89      private final Map<String, Future<Result>> parentCache;
90  
91      @SuppressWarnings("checkstyle:ParameterNumber")
92      public ProjectModelResolver(
93              RepositorySystemSession session,
94              RequestTrace trace,
95              RepositorySystem resolver,
96              RemoteRepositoryManager remoteRepositoryManager,
97              List<RemoteRepository> repositories,
98              ProjectBuildingRequest.RepositoryMerging repositoryMerging,
99              ReactorModelPool modelPool,
100             Map<String, Object> parentCache) {
101         this.session = session;
102         this.trace = trace;
103         this.resolver = resolver;
104         this.remoteRepositoryManager = remoteRepositoryManager;
105         this.pomRepositories = new ArrayList<>();
106         this.externalRepositories = Collections.unmodifiableList(new ArrayList<>(repositories));
107         this.repositories = new ArrayList<>();
108         this.repositories.addAll(externalRepositories);
109         this.repositoryMerging = repositoryMerging;
110         this.repositoryIds = new HashSet<>();
111         this.modelPool = modelPool;
112         this.parentCache = parentCache != null ? (Map) parentCache : new ConcurrentHashMap<>();
113     }
114 
115     private ProjectModelResolver(ProjectModelResolver original) {
116         this.session = original.session;
117         this.trace = original.trace;
118         this.resolver = original.resolver;
119         this.remoteRepositoryManager = original.remoteRepositoryManager;
120         this.pomRepositories = new ArrayList<>(original.pomRepositories);
121         this.externalRepositories = original.externalRepositories;
122         this.repositories = new ArrayList<>(original.repositories);
123         this.repositoryMerging = original.repositoryMerging;
124         this.repositoryIds = new HashSet<>(original.repositoryIds);
125         this.modelPool = original.modelPool;
126         this.parentCache = original.parentCache;
127     }
128 
129     public void addRepository(Repository repository) throws InvalidRepositoryException {
130         addRepository(repository, false);
131     }
132 
133     @Override
134     public void addRepository(final Repository repository, boolean replace) throws InvalidRepositoryException {
135         if (!repositoryIds.add(repository.getId())) {
136             if (!replace) {
137                 return;
138             }
139 
140             // Remove any previous repository with this Id
141             removeMatchingRepository(repositories, repository.getId());
142             removeMatchingRepository(pomRepositories, repository.getId());
143         }
144 
145         List<RemoteRepository> newRepositories =
146                 Collections.singletonList(ArtifactDescriptorUtils.toRemoteRepository(repository));
147 
148         if (ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT.equals(repositoryMerging)) {
149             repositories = remoteRepositoryManager.aggregateRepositories(session, repositories, newRepositories, true);
150         } else {
151             pomRepositories =
152                     remoteRepositoryManager.aggregateRepositories(session, pomRepositories, newRepositories, true);
153             repositories = remoteRepositoryManager.aggregateRepositories(
154                     session, pomRepositories, externalRepositories, false);
155         }
156     }
157 
158     private static void removeMatchingRepository(Iterable<RemoteRepository> repositories, final String id) {
159         Iterator<RemoteRepository> iterator = repositories.iterator();
160         while (iterator.hasNext()) {
161             RemoteRepository next = iterator.next();
162             if (next.getId().equals(id)) {
163                 iterator.remove();
164             }
165         }
166     }
167 
168     public ModelResolver newCopy() {
169         return new ProjectModelResolver(this);
170     }
171 
172     public ModelSource resolveModel(String groupId, String artifactId, String version)
173             throws UnresolvableModelException {
174         Artifact pomArtifact = new DefaultArtifact(groupId, artifactId, "", "pom", version);
175 
176         try {
177             ArtifactRequest request = new ArtifactRequest(pomArtifact, repositories, context);
178             request.setTrace(trace);
179             pomArtifact = resolver.resolveArtifact(session, request).getArtifact();
180         } catch (ArtifactResolutionException e) {
181             throw new UnresolvableModelException(e.getMessage(), groupId, artifactId, version, e);
182         }
183 
184         return new ArtifactModelSource(pomArtifact.getFile(), groupId, artifactId, version);
185     }
186 
187     record Result(ModelSource source, Parent parent, Exception e) {}
188 
189     @Override
190     public ModelSource resolveModel(final Parent parent, AtomicReference<Parent> modified)
191             throws UnresolvableModelException {
192         Result result;
193         try {
194             Future<Result> future = parentCache.computeIfAbsent(parent.getId(), id -> {
195                 ForkJoinPool pool = new ForkJoinPool(MAX_CAP);
196                 ForkJoinTask<Result> task = new ForkJoinTask<>() {
197                     Result result;
198 
199                     @Override
200                     public Result getRawResult() {
201                         return result;
202                     }
203 
204                     @Override
205                     protected void setRawResult(Result result) {
206                         this.result = result;
207                     }
208 
209                     @Override
210                     protected boolean exec() {
211                         try {
212                             AtomicReference<Parent> modified = new AtomicReference<>();
213                             ModelSource source = doResolveModel(parent, modified);
214                             result = new Result(source, modified.get(), null);
215                         } catch (Exception e) {
216                             result = new Result(null, null, e);
217                         } finally {
218                             pool.shutdown();
219                         }
220                         return true;
221                     }
222                 };
223                 pool.submit(task);
224                 return task;
225             });
226             result = future.get();
227         } catch (Exception e) {
228             throw new UnresolvableModelException(e, parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
229         }
230         if (result.e != null) {
231             uncheckedThrow(result.e);
232             return null;
233         } else {
234             if (result.parent != null && modified != null) {
235                 modified.set(result.parent);
236             }
237             return result.source;
238         }
239     }
240 
241     static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
242         throw (T) t; // rely on vacuous cast
243     }
244 
245     private ModelSource doResolveModel(Parent parent, AtomicReference<Parent> modified)
246             throws UnresolvableModelException {
247         try {
248             final Artifact artifact =
249                     new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion());
250 
251             final VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, repositories, context);
252             versionRangeRequest.setTrace(trace);
253 
254             final VersionRangeResult versionRangeResult = resolver.resolveVersionRange(session, versionRangeRequest);
255 
256             if (versionRangeResult.getHighestVersion() == null) {
257                 throw new UnresolvableModelException(
258                         String.format(
259                                 "No versions matched the requested parent version range '%s'", parent.getVersion()),
260                         parent.getGroupId(),
261                         parent.getArtifactId(),
262                         parent.getVersion());
263             }
264 
265             if (versionRangeResult.getVersionConstraint() != null
266                     && versionRangeResult.getVersionConstraint().getRange() != null
267                     && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
268                 // Message below is checked for in the MNG-2199 core IT.
269                 throw new UnresolvableModelException(
270                         String.format(
271                                 "The requested parent version range '%s' does not specify an upper bound",
272                                 parent.getVersion()),
273                         parent.getGroupId(),
274                         parent.getArtifactId(),
275                         parent.getVersion());
276             }
277 
278             String newVersion = versionRangeResult.getHighestVersion().toString();
279             if (!parent.getVersion().equals(newVersion)) {
280                 modified.set(parent.withVersion(newVersion));
281             }
282 
283             return resolveModel(parent.getGroupId(), parent.getArtifactId(), newVersion);
284         } catch (final VersionRangeResolutionException e) {
285             throw new UnresolvableModelException(
286                     e.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), e);
287         }
288     }
289 
290     @Override
291     public ModelSource resolveModel(final Dependency dependency, AtomicReference<Dependency> modified)
292             throws UnresolvableModelException {
293         try {
294             final Artifact artifact = new DefaultArtifact(
295                     dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion());
296 
297             final VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, repositories, context);
298             versionRangeRequest.setTrace(trace);
299 
300             final VersionRangeResult versionRangeResult = resolver.resolveVersionRange(session, versionRangeRequest);
301 
302             if (versionRangeResult.getHighestVersion() == null) {
303                 throw new UnresolvableModelException(
304                         String.format(
305                                 "No versions matched the requested dependency version range '%s'",
306                                 dependency.getVersion()),
307                         dependency.getGroupId(),
308                         dependency.getArtifactId(),
309                         dependency.getVersion());
310             }
311 
312             if (versionRangeResult.getVersionConstraint() != null
313                     && versionRangeResult.getVersionConstraint().getRange() != null
314                     && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
315                 // Message below is checked for in the MNG-4463 core IT.
316                 throw new UnresolvableModelException(
317                         String.format(
318                                 "The requested dependency version range '%s' does not specify an upper bound",
319                                 dependency.getVersion()),
320                         dependency.getGroupId(),
321                         dependency.getArtifactId(),
322                         dependency.getVersion());
323             }
324 
325             String newVersion = versionRangeResult.getHighestVersion().toString();
326             if (!dependency.getVersion().equals(newVersion)) {
327                 modified.set(dependency.withVersion(newVersion));
328             }
329 
330             if (modelPool != null) {
331                 Model model = modelPool.get(dependency.getGroupId(), dependency.getArtifactId(), newVersion);
332 
333                 if (model != null) {
334                     return new FileModelSource(model.getPomFile());
335                 }
336             }
337 
338             return resolveModel(dependency.getGroupId(), dependency.getArtifactId(), newVersion);
339         } catch (VersionRangeResolutionException e) {
340             throw new UnresolvableModelException(
341                     e.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), e);
342         }
343     }
344 
345     @Override
346     public ModelSource resolveModel(org.apache.maven.model.Parent parent) throws UnresolvableModelException {
347         AtomicReference<org.apache.maven.api.model.Parent> resolvedParent = new AtomicReference<>();
348         ModelSource result = resolveModel(parent.getDelegate(), resolvedParent);
349         if (resolvedParent.get() != null) {
350             parent.setVersion(resolvedParent.get().getVersion());
351         }
352         return result;
353     }
354 
355     @Override
356     public ModelSource resolveModel(org.apache.maven.model.Dependency dependency) throws UnresolvableModelException {
357         AtomicReference<org.apache.maven.api.model.Dependency> resolvedDependency = new AtomicReference<>();
358         ModelSource result = resolveModel(dependency.getDelegate(), resolvedDependency);
359         if (resolvedDependency.get() != null) {
360             dependency.setVersion(resolvedDependency.get().getVersion());
361         }
362         return result;
363     }
364 
365     @Override
366     public void addRepository(org.apache.maven.model.Repository repository) throws InvalidRepositoryException {
367         addRepository(repository.getDelegate());
368     }
369 
370     @Override
371     public void addRepository(org.apache.maven.model.Repository repository, boolean replace)
372             throws InvalidRepositoryException {
373         addRepository(repository.getDelegate(), replace);
374     }
375 }