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