1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
60
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
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;
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
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
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 }