1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.impl.model;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.lang.reflect.Field;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.LinkedHashMap;
34 import java.util.LinkedHashSet;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.Set;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.CopyOnWriteArrayList;
41 import java.util.concurrent.Executor;
42 import java.util.concurrent.Executors;
43 import java.util.concurrent.atomic.AtomicReference;
44 import java.util.function.Supplier;
45 import java.util.function.UnaryOperator;
46 import java.util.stream.Collectors;
47 import java.util.stream.Stream;
48
49 import org.apache.maven.api.Constants;
50 import org.apache.maven.api.RemoteRepository;
51 import org.apache.maven.api.Session;
52 import org.apache.maven.api.Type;
53 import org.apache.maven.api.VersionRange;
54 import org.apache.maven.api.annotations.Nonnull;
55 import org.apache.maven.api.annotations.Nullable;
56 import org.apache.maven.api.cache.CacheMetadata;
57 import org.apache.maven.api.cache.CacheRetention;
58 import org.apache.maven.api.di.Inject;
59 import org.apache.maven.api.di.Named;
60 import org.apache.maven.api.di.Singleton;
61 import org.apache.maven.api.feature.Features;
62 import org.apache.maven.api.model.Activation;
63 import org.apache.maven.api.model.Dependency;
64 import org.apache.maven.api.model.DependencyManagement;
65 import org.apache.maven.api.model.Exclusion;
66 import org.apache.maven.api.model.InputLocation;
67 import org.apache.maven.api.model.InputSource;
68 import org.apache.maven.api.model.Model;
69 import org.apache.maven.api.model.Parent;
70 import org.apache.maven.api.model.Profile;
71 import org.apache.maven.api.services.BuilderProblem;
72 import org.apache.maven.api.services.BuilderProblem.Severity;
73 import org.apache.maven.api.services.Interpolator;
74 import org.apache.maven.api.services.MavenException;
75 import org.apache.maven.api.services.ModelBuilder;
76 import org.apache.maven.api.services.ModelBuilderException;
77 import org.apache.maven.api.services.ModelBuilderRequest;
78 import org.apache.maven.api.services.ModelBuilderResult;
79 import org.apache.maven.api.services.ModelProblem;
80 import org.apache.maven.api.services.ModelProblem.Version;
81 import org.apache.maven.api.services.ModelProblemCollector;
82 import org.apache.maven.api.services.ModelSource;
83 import org.apache.maven.api.services.ProblemCollector;
84 import org.apache.maven.api.services.RepositoryFactory;
85 import org.apache.maven.api.services.Request;
86 import org.apache.maven.api.services.RequestTrace;
87 import org.apache.maven.api.services.Result;
88 import org.apache.maven.api.services.Source;
89 import org.apache.maven.api.services.Sources;
90 import org.apache.maven.api.services.SuperPomProvider;
91 import org.apache.maven.api.services.VersionParserException;
92 import org.apache.maven.api.services.model.DependencyManagementImporter;
93 import org.apache.maven.api.services.model.DependencyManagementInjector;
94 import org.apache.maven.api.services.model.InheritanceAssembler;
95 import org.apache.maven.api.services.model.ModelInterpolator;
96 import org.apache.maven.api.services.model.ModelNormalizer;
97 import org.apache.maven.api.services.model.ModelPathTranslator;
98 import org.apache.maven.api.services.model.ModelProcessor;
99 import org.apache.maven.api.services.model.ModelResolver;
100 import org.apache.maven.api.services.model.ModelResolverException;
101 import org.apache.maven.api.services.model.ModelUrlNormalizer;
102 import org.apache.maven.api.services.model.ModelValidator;
103 import org.apache.maven.api.services.model.ModelVersionParser;
104 import org.apache.maven.api.services.model.PathTranslator;
105 import org.apache.maven.api.services.model.PluginConfigurationExpander;
106 import org.apache.maven.api.services.model.PluginManagementInjector;
107 import org.apache.maven.api.services.model.ProfileInjector;
108 import org.apache.maven.api.services.model.ProfileSelector;
109 import org.apache.maven.api.services.model.RootLocator;
110 import org.apache.maven.api.services.xml.XmlReaderException;
111 import org.apache.maven.api.services.xml.XmlReaderRequest;
112 import org.apache.maven.api.spi.ModelParserException;
113 import org.apache.maven.api.spi.ModelTransformer;
114 import org.apache.maven.impl.InternalSession;
115 import org.apache.maven.impl.RequestTraceHelper;
116 import org.apache.maven.impl.util.PhasingExecutor;
117 import org.slf4j.Logger;
118 import org.slf4j.LoggerFactory;
119
120
121
122
123
124
125
126 @Named
127 @Singleton
128 public class DefaultModelBuilder implements ModelBuilder {
129
130 public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
131 private static final String RAW = "raw";
132 private static final String FILE = "file";
133 private static final String IMPORT = "import";
134 private static final String PARENT = "parent";
135 private static final String MODEL = "model";
136
137 private final Logger logger = LoggerFactory.getLogger(getClass());
138
139 private final ModelProcessor modelProcessor;
140 private final ModelValidator modelValidator;
141 private final ModelNormalizer modelNormalizer;
142 private final ModelInterpolator modelInterpolator;
143 private final ModelPathTranslator modelPathTranslator;
144 private final ModelUrlNormalizer modelUrlNormalizer;
145 private final SuperPomProvider superPomProvider;
146 private final InheritanceAssembler inheritanceAssembler;
147 private final ProfileSelector profileSelector;
148 private final ProfileInjector profileInjector;
149 private final PluginManagementInjector pluginManagementInjector;
150 private final DependencyManagementInjector dependencyManagementInjector;
151 private final DependencyManagementImporter dependencyManagementImporter;
152 private final PluginConfigurationExpander pluginConfigurationExpander;
153 private final ModelVersionParser versionParser;
154 private final List<ModelTransformer> transformers;
155 private final ModelResolver modelResolver;
156 private final Interpolator interpolator;
157 private final PathTranslator pathTranslator;
158 private final RootLocator rootLocator;
159
160 @SuppressWarnings("checkstyle:ParameterNumber")
161 @Inject
162 public DefaultModelBuilder(
163 ModelProcessor modelProcessor,
164 ModelValidator modelValidator,
165 ModelNormalizer modelNormalizer,
166 ModelInterpolator modelInterpolator,
167 ModelPathTranslator modelPathTranslator,
168 ModelUrlNormalizer modelUrlNormalizer,
169 SuperPomProvider superPomProvider,
170 InheritanceAssembler inheritanceAssembler,
171 ProfileSelector profileSelector,
172 ProfileInjector profileInjector,
173 PluginManagementInjector pluginManagementInjector,
174 DependencyManagementInjector dependencyManagementInjector,
175 DependencyManagementImporter dependencyManagementImporter,
176 PluginConfigurationExpander pluginConfigurationExpander,
177 ModelVersionParser versionParser,
178 @Nullable List<ModelTransformer> transformers,
179 ModelResolver modelResolver,
180 Interpolator interpolator,
181 PathTranslator pathTranslator,
182 RootLocator rootLocator) {
183 this.modelProcessor = modelProcessor;
184 this.modelValidator = modelValidator;
185 this.modelNormalizer = modelNormalizer;
186 this.modelInterpolator = modelInterpolator;
187 this.modelPathTranslator = modelPathTranslator;
188 this.modelUrlNormalizer = modelUrlNormalizer;
189 this.superPomProvider = superPomProvider;
190 this.inheritanceAssembler = inheritanceAssembler;
191 this.profileSelector = profileSelector;
192 this.profileInjector = profileInjector;
193 this.pluginManagementInjector = pluginManagementInjector;
194 this.dependencyManagementInjector = dependencyManagementInjector;
195 this.dependencyManagementImporter = dependencyManagementImporter;
196 this.pluginConfigurationExpander = pluginConfigurationExpander;
197 this.versionParser = versionParser;
198 this.transformers = transformers;
199 this.modelResolver = modelResolver;
200 this.interpolator = interpolator;
201 this.pathTranslator = pathTranslator;
202 this.rootLocator = rootLocator;
203 }
204
205 @Override
206 public ModelBuilderSession newSession() {
207 return new ModelBuilderSessionImpl();
208 }
209
210 protected class ModelBuilderSessionImpl implements ModelBuilderSession {
211 ModelBuilderSessionState mainSession;
212
213
214
215
216
217
218
219
220 @Override
221 public ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilderException {
222 RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(request.getSession(), request);
223 try {
224
225 ModelBuilderSessionState session;
226 if (mainSession == null) {
227 mainSession = new ModelBuilderSessionState(request);
228 session = mainSession;
229 } else {
230 session = mainSession.derive(
231 request,
232 new DefaultModelBuilderResult(request, ProblemCollector.create(mainSession.session)));
233 }
234
235 if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
236
237 session.buildBuildPom();
238 } else {
239
240 session.buildEffectiveModel(new LinkedHashSet<>());
241 }
242 return session.result;
243 } finally {
244 RequestTraceHelper.exit(trace);
245 }
246 }
247 }
248
249 protected class ModelBuilderSessionState implements ModelProblemCollector {
250 final Session session;
251 final ModelBuilderRequest request;
252 final DefaultModelBuilderResult result;
253 final Graph dag;
254 final Map<GAKey, Set<ModelSource>> mappedSources;
255
256 String source;
257 Model sourceModel;
258 Model rootModel;
259
260 List<RemoteRepository> pomRepositories;
261 List<RemoteRepository> externalRepositories;
262 List<RemoteRepository> repositories;
263
264 ModelBuilderSessionState(ModelBuilderRequest request) {
265 this(
266 request.getSession(),
267 request,
268 new DefaultModelBuilderResult(request, ProblemCollector.create(request.getSession())),
269 new Graph(),
270 new ConcurrentHashMap<>(64),
271 List.of(),
272 repos(request),
273 repos(request));
274 }
275
276 static List<RemoteRepository> repos(ModelBuilderRequest request) {
277 return List.copyOf(
278 request.getRepositories() != null
279 ? request.getRepositories()
280 : request.getSession().getRemoteRepositories());
281 }
282
283 @SuppressWarnings("checkstyle:ParameterNumber")
284 private ModelBuilderSessionState(
285 Session session,
286 ModelBuilderRequest request,
287 DefaultModelBuilderResult result,
288 Graph dag,
289 Map<GAKey, Set<ModelSource>> mappedSources,
290 List<RemoteRepository> pomRepositories,
291 List<RemoteRepository> externalRepositories,
292 List<RemoteRepository> repositories) {
293 this.session = session;
294 this.request = request;
295 this.result = result;
296 this.dag = dag;
297 this.mappedSources = mappedSources;
298 this.pomRepositories = pomRepositories;
299 this.externalRepositories = externalRepositories;
300 this.repositories = repositories;
301 this.result.setSource(this.request.getSource());
302 }
303
304 ModelBuilderSessionState derive(ModelSource source) {
305 return derive(source, new DefaultModelBuilderResult(request, ProblemCollector.create(session)));
306 }
307
308 ModelBuilderSessionState derive(ModelSource source, DefaultModelBuilderResult result) {
309 return derive(ModelBuilderRequest.build(request, source), result);
310 }
311
312
313
314
315 ModelBuilderSessionState derive(ModelBuilderRequest request) {
316 return derive(request, new DefaultModelBuilderResult(request, ProblemCollector.create(session)));
317 }
318
319 ModelBuilderSessionState derive(ModelBuilderRequest request, DefaultModelBuilderResult result) {
320 if (session != request.getSession()) {
321 throw new IllegalArgumentException("Session mismatch");
322 }
323 return new ModelBuilderSessionState(
324 session, request, result, dag, mappedSources, pomRepositories, externalRepositories, repositories);
325 }
326
327 @Override
328 public String toString() {
329 return "ModelBuilderSession[" + "session="
330 + session + ", " + "request="
331 + request + ", " + "result="
332 + result + ']';
333 }
334
335 PhasingExecutor createExecutor() {
336 return new PhasingExecutor(Executors.newFixedThreadPool(getParallelism()));
337 }
338
339 private int getParallelism() {
340 int parallelism = Runtime.getRuntime().availableProcessors() / 2 + 1;
341 try {
342 String str = request.getUserProperties().get(Constants.MAVEN_MODEL_BUILDER_PARALLELISM);
343 if (str != null) {
344 parallelism = Integer.parseInt(str);
345 }
346 } catch (Exception e) {
347
348 }
349 return Math.max(1, Math.min(parallelism, Runtime.getRuntime().availableProcessors()));
350 }
351
352 public Model getRawModel(Path from, String groupId, String artifactId) {
353 ModelSource source = getSource(groupId, artifactId);
354 if (source != null) {
355 if (addEdge(from, source.getPath())) {
356 return null;
357 }
358 try {
359 return derive(source).readRawModel();
360 } catch (ModelBuilderException e) {
361
362 }
363 }
364 return null;
365 }
366
367 public Model getRawModel(Path from, Path path) {
368 if (!Files.isRegularFile(path)) {
369 throw new IllegalArgumentException("Not a regular file: " + path);
370 }
371 if (addEdge(from, path)) {
372 return null;
373 }
374 try {
375 return derive(Sources.buildSource(path)).readRawModel();
376 } catch (ModelBuilderException e) {
377
378 }
379 return null;
380 }
381
382
383
384
385 private boolean addEdge(Path from, Path p) {
386 try {
387 dag.addEdge(from.toString(), p.toString());
388 return false;
389 } catch (Graph.CycleDetectedException e) {
390 add(Severity.FATAL, Version.BASE, "Cycle detected between models at " + from + " and " + p, null, e);
391 return true;
392 }
393 }
394
395 public ModelSource getSource(String groupId, String artifactId) {
396 Set<ModelSource> sources = mappedSources.get(new GAKey(groupId, artifactId));
397 if (sources != null) {
398 return sources.stream()
399 .reduce((a, b) -> {
400 throw new IllegalStateException(String.format(
401 "No unique Source for %s:%s: %s and %s",
402 groupId, artifactId, a.getLocation(), b.getLocation()));
403 })
404 .orElse(null);
405 }
406 return null;
407 }
408
409 public void putSource(String groupId, String artifactId, ModelSource source) {
410 mappedSources
411 .computeIfAbsent(new GAKey(groupId, artifactId), k -> new HashSet<>())
412 .add(source);
413
414 if (groupId != null) {
415 putSource(null, artifactId, source);
416 }
417 }
418
419 @Override
420 public ProblemCollector<ModelProblem> getProblemCollector() {
421 return result.getProblemCollector();
422 }
423
424 @Override
425 public void setSource(String source) {
426 this.source = source;
427 this.sourceModel = null;
428 }
429
430 @Override
431 public void setSource(Model source) {
432 this.sourceModel = source;
433 this.source = null;
434
435 if (rootModel == null) {
436 rootModel = source;
437 }
438 }
439
440 @Override
441 public String getSource() {
442 if (source == null && sourceModel != null) {
443 source = ModelProblemUtils.toPath(sourceModel);
444 }
445 return source;
446 }
447
448 private String getModelId() {
449 return ModelProblemUtils.toId(sourceModel);
450 }
451
452 @Override
453 public void setRootModel(Model rootModel) {
454 this.rootModel = rootModel;
455 }
456
457 @Override
458 public Model getRootModel() {
459 return rootModel;
460 }
461
462 @Override
463 public void add(
464 BuilderProblem.Severity severity,
465 ModelProblem.Version version,
466 String message,
467 InputLocation location,
468 Exception exception) {
469 int line = -1;
470 int column = -1;
471 String source = null;
472 String modelId = null;
473
474 if (location != null) {
475 line = location.getLineNumber();
476 column = location.getColumnNumber();
477 if (location.getSource() != null) {
478 modelId = location.getSource().getModelId();
479 source = location.getSource().getLocation();
480 }
481 }
482
483 if (modelId == null) {
484 modelId = getModelId();
485 source = getSource();
486 }
487
488 if (line <= 0 && column <= 0 && exception instanceof ModelParserException e) {
489 line = e.getLineNumber();
490 column = e.getColumnNumber();
491 }
492
493 ModelProblem problem =
494 new DefaultModelProblem(message, severity, version, source, line, column, modelId, exception);
495
496 add(problem);
497 }
498
499 @Override
500 public ModelBuilderException newModelBuilderException() {
501 return new ModelBuilderException(result);
502 }
503
504 public void mergeRepositories(Model model, boolean replace) {
505 if (model.getRepositories().isEmpty()) {
506 return;
507 }
508
509 Model interpolatedModel = interpolateModel(
510 Model.newBuilder()
511 .pomFile(model.getPomFile())
512 .repositories(model.getRepositories())
513 .build(),
514 request,
515 this);
516 List<RemoteRepository> repos = interpolatedModel.getRepositories().stream()
517 .map(session::createRemoteRepository)
518 .toList();
519 if (replace) {
520 Set<String> ids = repos.stream().map(RemoteRepository::getId).collect(Collectors.toSet());
521 repositories = repositories.stream()
522 .filter(r -> !ids.contains(r.getId()))
523 .toList();
524 pomRepositories = pomRepositories.stream()
525 .filter(r -> !ids.contains(r.getId()))
526 .toList();
527 } else {
528 Set<String> ids =
529 pomRepositories.stream().map(RemoteRepository::getId).collect(Collectors.toSet());
530 repos = repos.stream().filter(r -> !ids.contains(r.getId())).toList();
531 }
532
533 RepositoryFactory repositoryFactory = session.getService(RepositoryFactory.class);
534 if (request.getRepositoryMerging() == ModelBuilderRequest.RepositoryMerging.REQUEST_DOMINANT) {
535 repositories = repositoryFactory.aggregate(session, repositories, repos, true);
536 pomRepositories = repositories;
537 } else {
538 pomRepositories = repositoryFactory.aggregate(session, pomRepositories, repos, true);
539 repositories = repositoryFactory.aggregate(session, pomRepositories, externalRepositories, false);
540 }
541 }
542
543
544
545
546
547 Model transformFileToRaw(Model model) {
548 if (model.getDependencies().isEmpty()) {
549 return model;
550 }
551 List<Dependency> newDeps = new ArrayList<>(model.getDependencies().size());
552 boolean changed = false;
553 for (Dependency dep : model.getDependencies()) {
554 Dependency newDep = null;
555 if (dep.getVersion() == null) {
556 newDep = inferDependencyVersion(model, dep);
557 if (newDep != null) {
558 changed = true;
559 }
560 }
561 newDeps.add(newDep == null ? dep : newDep);
562 }
563 return changed ? model.withDependencies(newDeps) : model;
564 }
565
566 private Dependency inferDependencyVersion(Model model, Dependency dep) {
567 Model depModel = getRawModel(model.getPomFile(), dep.getGroupId(), dep.getArtifactId());
568 if (depModel == null) {
569 return null;
570 }
571 Dependency.Builder depBuilder = Dependency.newBuilder(dep);
572 String version = depModel.getVersion();
573 InputLocation versionLocation = depModel.getLocation("version");
574 if (version == null && depModel.getParent() != null) {
575 version = depModel.getParent().getVersion();
576 versionLocation = depModel.getParent().getLocation("version");
577 }
578 depBuilder.version(version).location("version", versionLocation);
579 if (dep.getGroupId() == null) {
580 String depGroupId = depModel.getGroupId();
581 InputLocation groupIdLocation = depModel.getLocation("groupId");
582 if (depGroupId == null && depModel.getParent() != null) {
583 depGroupId = depModel.getParent().getGroupId();
584 groupIdLocation = depModel.getParent().getLocation("groupId");
585 }
586 depBuilder.groupId(depGroupId).location("groupId", groupIdLocation);
587 }
588 return depBuilder.build();
589 }
590
591 String replaceCiFriendlyVersion(Map<String, String> properties, String version) {
592 return version != null ? interpolator.interpolate(version, properties::get) : null;
593 }
594
595 private void buildBuildPom() throws ModelBuilderException {
596
597 Path top = request.getSource().getPath();
598 if (top == null) {
599 throw new IllegalStateException("Recursive build requested but source has no path");
600 }
601 top = top.toAbsolutePath().normalize();
602
603
604 Path rootDirectory;
605 try {
606 rootDirectory = session.getRootDirectory();
607 } catch (IllegalStateException e) {
608 rootDirectory = session.getService(RootLocator.class).findMandatoryRoot(top);
609 }
610
611
612 Path root = modelProcessor.locateExistingPom(rootDirectory);
613 if (root != null) {
614 root = root.toAbsolutePath().normalize();
615 } else {
616 root = top;
617 }
618
619
620 loadFromRoot(root, top);
621
622
623 if (hasErrors()) {
624 throw newModelBuilderException();
625 }
626
627
628
629 var allResults = results(result).toList();
630 List<RuntimeException> exceptions = new CopyOnWriteArrayList<>();
631 InternalSession session = InternalSession.from(this.session);
632 RequestTrace trace = session.getCurrentTrace();
633 try (PhasingExecutor executor = createExecutor()) {
634 for (DefaultModelBuilderResult r : allResults) {
635 executor.execute(() -> {
636 ModelBuilderSessionState mbs = derive(r.getSource(), r);
637 session.setCurrentTrace(trace);
638 try {
639 mbs.buildEffectiveModel(new LinkedHashSet<>());
640 } catch (ModelBuilderException e) {
641
642 } catch (RuntimeException t) {
643 exceptions.add(t);
644 } finally {
645 session.setCurrentTrace(null);
646 }
647 });
648 }
649 }
650
651
652 if (exceptions.size() == 1) {
653 throw exceptions.get(0);
654 } else if (exceptions.size() > 1) {
655 MavenException fatalException = new MavenException("Multiple fatal exceptions occurred");
656 exceptions.forEach(fatalException::addSuppressed);
657 throw fatalException;
658 } else if (hasErrors()) {
659 throw newModelBuilderException();
660 }
661 }
662
663
664
665
666
667
668
669
670
671 Stream<DefaultModelBuilderResult> results(DefaultModelBuilderResult r) {
672 return Stream.concat(Stream.of(r), r.getChildren().stream().flatMap(this::results));
673 }
674
675 private void loadFromRoot(Path root, Path top) {
676 try (PhasingExecutor executor = createExecutor()) {
677 DefaultModelBuilderResult r = Objects.equals(top, root)
678 ? result
679 : new DefaultModelBuilderResult(request, ProblemCollector.create(session));
680 loadFilePom(executor, top, root, Set.of(), r);
681 }
682 if (result.getFileModel() == null && !Objects.equals(top, root)) {
683 logger.warn(
684 "The top project ({}) cannot be found in the reactor from root project ({}). "
685 + "Make sure the root directory is correct (a missing '.mvn' directory in the root "
686 + "project is the most common cause) and the project is correctly included "
687 + "in the reactor (missing activated profiles, command line options, etc.). For this "
688 + "build, the top project will be used as the root project.",
689 top,
690 root);
691 mappedSources.clear();
692 loadFromRoot(top, top);
693 }
694 }
695
696 private void loadFilePom(
697 Executor executor, Path top, Path pom, Set<Path> parents, DefaultModelBuilderResult r) {
698 try {
699 Path pomDirectory = Files.isDirectory(pom) ? pom : pom.getParent();
700 ModelSource src = Sources.buildSource(pom);
701 Model model = derive(src, r).readFileModel();
702
703
704 putSource(getGroupId(model), model.getArtifactId(), src);
705 Model activated = activateFileModel(model);
706 for (String subproject : getSubprojects(activated)) {
707 if (subproject == null || subproject.isEmpty()) {
708 continue;
709 }
710
711 subproject = subproject.replace('\\', File.separatorChar).replace('/', File.separatorChar);
712
713 Path rawSubprojectFile = modelProcessor.locateExistingPom(pomDirectory.resolve(subproject));
714
715 if (rawSubprojectFile == null) {
716 ModelProblem problem = new DefaultModelProblem(
717 "Child subproject " + subproject + " of " + pomDirectory + " does not exist",
718 Severity.ERROR,
719 Version.BASE,
720 model,
721 -1,
722 -1,
723 null);
724 r.getProblemCollector().reportProblem(problem);
725 continue;
726 }
727
728 Path subprojectFile = rawSubprojectFile.toAbsolutePath().normalize();
729
730 if (parents.contains(subprojectFile)) {
731 StringBuilder buffer = new StringBuilder(256);
732 for (Path aggregatorFile : parents) {
733 buffer.append(aggregatorFile).append(" -> ");
734 }
735 buffer.append(subprojectFile);
736
737 ModelProblem problem = new DefaultModelProblem(
738 "Child subproject " + subprojectFile + " of " + pom + " forms aggregation cycle "
739 + buffer,
740 Severity.ERROR,
741 Version.BASE,
742 model,
743 -1,
744 -1,
745 null);
746 r.getProblemCollector().reportProblem(problem);
747 continue;
748 }
749
750 DefaultModelBuilderResult cr = Objects.equals(top, subprojectFile)
751 ? result
752 : new DefaultModelBuilderResult(request, ProblemCollector.create(session));
753 if (request.isRecursive()) {
754 r.getChildren().add(cr);
755 }
756
757 InternalSession session = InternalSession.from(this.session);
758 RequestTrace trace = session.getCurrentTrace();
759 executor.execute(() -> {
760 session.setCurrentTrace(trace);
761 try {
762 loadFilePom(executor, top, subprojectFile, concat(parents, pom), cr);
763 } finally {
764 session.setCurrentTrace(null);
765 }
766 });
767 }
768 } catch (ModelBuilderException e) {
769
770 add(Severity.ERROR, Version.V40, "Failed to load project " + pom, e);
771 }
772 }
773
774 static <T> Set<T> concat(Set<T> a, T b) {
775 Set<T> result = new HashSet<>(a);
776 result.add(b);
777 return Set.copyOf(result);
778 }
779
780 void buildEffectiveModel(Collection<String> importIds) throws ModelBuilderException {
781 Model resultModel = readEffectiveModel();
782 setSource(resultModel);
783 setRootModel(resultModel);
784
785
786 resultModel =
787 modelPathTranslator.alignToBaseDirectory(resultModel, resultModel.getProjectDirectory(), request);
788
789
790 resultModel = pluginManagementInjector.injectManagement(resultModel, request, this);
791
792
793 if (request.getRequestType() != ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY) {
794 org.apache.maven.api.services.ModelTransformer lifecycleBindingsInjector =
795 request.getLifecycleBindingsInjector();
796 if (lifecycleBindingsInjector != null) {
797 resultModel = lifecycleBindingsInjector.transform(resultModel, request, this);
798 }
799 }
800
801
802 resultModel = importDependencyManagement(resultModel, importIds);
803
804
805 resultModel = dependencyManagementInjector.injectManagement(resultModel, request, this);
806
807 resultModel = modelNormalizer.injectDefaultValues(resultModel, request, this);
808
809 if (request.getRequestType() != ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY) {
810
811 resultModel = pluginConfigurationExpander.expandPluginConfiguration(resultModel, request, this);
812 }
813
814 for (var transformer : transformers) {
815 resultModel = transformer.transformEffectiveModel(resultModel);
816 }
817
818 result.setEffectiveModel(resultModel);
819
820 if (result.getFileModel().getParent() != null
821 && result.getFileModel().getParent().getRelativePath() == null) {
822 result.setFileModel(result.getFileModel()
823 .withParent(result.getFileModel()
824 .getParent()
825 .withRelativePath(resultModel.getParent().getRelativePath())));
826 }
827
828
829 modelValidator.validateEffectiveModel(
830 session,
831 resultModel,
832 isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL,
833 this);
834
835 if (hasErrors()) {
836 throw newModelBuilderException();
837 }
838 }
839
840 Model readParent(Model childModel, DefaultProfileActivationContext profileActivationContext) {
841 Model parentModel;
842
843 Parent parent = childModel.getParent();
844 if (parent != null) {
845 parentModel = resolveParent(childModel, profileActivationContext);
846
847 if (!"pom".equals(parentModel.getPackaging())) {
848 add(
849 Severity.ERROR,
850 Version.BASE,
851 "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint(parentModel)
852 + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"",
853 parentModel.getLocation("packaging"));
854 }
855 result.setParentModel(parentModel);
856 } else {
857 String superModelVersion = childModel.getModelVersion();
858 if (superModelVersion == null || !KNOWN_MODEL_VERSIONS.contains(superModelVersion)) {
859
860
861
862 superModelVersion = MODEL_VERSION_4_0_0;
863 }
864 parentModel = getSuperModel(superModelVersion);
865 }
866
867 return parentModel;
868 }
869
870 private Model resolveParent(Model childModel, DefaultProfileActivationContext profileActivationContext)
871 throws ModelBuilderException {
872 Model parentModel = null;
873 if (isBuildRequest()) {
874 parentModel = readParentLocally(childModel, profileActivationContext);
875 }
876 if (parentModel == null) {
877 parentModel = resolveAndReadParentExternally(childModel, profileActivationContext);
878 }
879 return parentModel;
880 }
881
882 private Model readParentLocally(Model childModel, DefaultProfileActivationContext profileActivationContext)
883 throws ModelBuilderException {
884 ModelSource candidateSource;
885
886 Parent parent = childModel.getParent();
887 String parentPath = parent.getRelativePath();
888 if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
889 if (parentPath != null && !parentPath.isEmpty()) {
890 candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, parentPath);
891 if (candidateSource == null) {
892 wrongParentRelativePath(childModel);
893 return null;
894 }
895 } else {
896 candidateSource =
897 resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
898 if (candidateSource == null && parentPath == null) {
899 candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, "..");
900 }
901 }
902 } else {
903 candidateSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
904 if (candidateSource == null) {
905 if (parentPath == null) {
906 parentPath = "..";
907 }
908 if (!parentPath.isEmpty()) {
909 candidateSource = request.getSource().resolve(modelProcessor::locateExistingPom, parentPath);
910 }
911 }
912 }
913
914 if (candidateSource == null) {
915 return null;
916 }
917
918 ModelBuilderSessionState derived = derive(candidateSource);
919 Model candidateModel = derived.readAsParentModel(profileActivationContext);
920 addActivePomProfiles(derived.result.getActivePomProfiles());
921
922 String groupId = getGroupId(candidateModel);
923 String artifactId = candidateModel.getArtifactId();
924 String version = getVersion(candidateModel);
925
926
927 if (groupId == null
928 || !groupId.equals(parent.getGroupId())
929 || artifactId == null
930 || !artifactId.equals(parent.getArtifactId())) {
931 mismatchRelativePathAndGA(childModel, groupId, artifactId);
932 return null;
933 }
934
935 if (version != null && parent.getVersion() != null && !version.equals(parent.getVersion())) {
936 try {
937 VersionRange parentRange = versionParser.parseVersionRange(parent.getVersion());
938 if (!parentRange.contains(versionParser.parseVersion(version))) {
939
940 return null;
941 }
942
943
944 String rawChildModelVersion = childModel.getVersion();
945
946 if (rawChildModelVersion == null) {
947
948 add(Severity.FATAL, Version.V31, "Version must be a constant", childModel.getLocation(""));
949
950 } else {
951 if (rawChildVersionReferencesParent(rawChildModelVersion)) {
952
953 add(
954 Severity.FATAL,
955 Version.V31,
956 "Version must be a constant",
957 childModel.getLocation("version"));
958 }
959 }
960
961
962 } catch (VersionParserException e) {
963
964 return null;
965 }
966 }
967 return candidateModel;
968 }
969
970 private void mismatchRelativePathAndGA(Model childModel, String groupId, String artifactId) {
971 Parent parent = childModel.getParent();
972 StringBuilder buffer = new StringBuilder(256);
973 buffer.append("'parent.relativePath'");
974 if (childModel != getRootModel()) {
975 buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel));
976 }
977 buffer.append(" points at ").append(groupId).append(':').append(artifactId);
978 buffer.append(" instead of ").append(parent.getGroupId()).append(':');
979 buffer.append(parent.getArtifactId()).append(", please verify your project structure");
980
981 setSource(childModel);
982 boolean warn = MODEL_VERSION_4_0_0.equals(childModel.getModelVersion())
983 || childModel.getParent().getRelativePath() == null;
984 add(warn ? Severity.WARNING : Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""));
985 }
986
987 private void wrongParentRelativePath(Model childModel) {
988 Parent parent = childModel.getParent();
989 String parentPath = parent.getRelativePath();
990 StringBuilder buffer = new StringBuilder(256);
991 buffer.append("'parent.relativePath'");
992 if (childModel != getRootModel()) {
993 buffer.append(" of POM ").append(ModelProblemUtils.toSourceHint(childModel));
994 }
995 buffer.append(" points at '").append(parentPath);
996 buffer.append("' but no POM could be found, please verify your project structure");
997
998 setSource(childModel);
999 add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""));
1000 }
1001
1002 Model resolveAndReadParentExternally(Model childModel, DefaultProfileActivationContext profileActivationContext)
1003 throws ModelBuilderException {
1004 ModelBuilderRequest request = this.request;
1005 setSource(childModel);
1006
1007 Parent parent = childModel.getParent();
1008
1009 String groupId = parent.getGroupId();
1010 String artifactId = parent.getArtifactId();
1011 String version = parent.getVersion();
1012
1013
1014 if (!childModel.getRepositories().isEmpty()) {
1015 var previousRepositories = repositories;
1016 mergeRepositories(childModel, false);
1017 if (!Objects.equals(previousRepositories, repositories)) {
1018 if (logger.isDebugEnabled()) {
1019 logger.debug("Merging repositories from " + childModel.getId() + "\n"
1020 + repositories.stream()
1021 .map(Object::toString)
1022 .collect(Collectors.joining("\n", " ", "")));
1023 }
1024 }
1025 }
1026
1027 ModelSource modelSource;
1028 try {
1029 modelSource = resolveReactorModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
1030 if (modelSource == null) {
1031 AtomicReference<Parent> modified = new AtomicReference<>();
1032 modelSource = modelResolver.resolveModel(request.getSession(), repositories, parent, modified);
1033 if (modified.get() != null) {
1034 parent = modified.get();
1035 }
1036 }
1037 } catch (ModelResolverException e) {
1038
1039 StringBuilder buffer = new StringBuilder(256);
1040 buffer.append("Non-resolvable parent POM");
1041 if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
1042 buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
1043 }
1044 if (childModel != getRootModel()) {
1045 buffer.append(" for ").append(ModelProblemUtils.toId(childModel));
1046 }
1047 buffer.append(": ").append(e.getMessage());
1048 if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT) {
1049 buffer.append(" and parent could not be found in reactor");
1050 }
1051
1052 add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""), e);
1053 throw newModelBuilderException();
1054 }
1055
1056 ModelBuilderRequest lenientRequest = ModelBuilderRequest.builder(request)
1057 .requestType(ModelBuilderRequest.RequestType.CONSUMER_PARENT)
1058 .source(modelSource)
1059 .build();
1060
1061 Model parentModel = derive(lenientRequest).readAsParentModel(profileActivationContext);
1062
1063 if (!parent.getVersion().equals(version)) {
1064 String rawChildModelVersion = childModel.getVersion();
1065
1066 if (rawChildModelVersion == null) {
1067
1068 add(Severity.FATAL, Version.V31, "Version must be a constant", childModel.getLocation(""));
1069 } else {
1070 if (rawChildVersionReferencesParent(rawChildModelVersion)) {
1071
1072 add(
1073 Severity.FATAL,
1074 Version.V31,
1075 "Version must be a constant",
1076 childModel.getLocation("version"));
1077 }
1078 }
1079
1080
1081 }
1082
1083 return parentModel;
1084 }
1085
1086 Model activateFileModel(Model inputModel) throws ModelBuilderException {
1087 setRootModel(inputModel);
1088
1089
1090 DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel);
1091
1092 setSource("(external profiles)");
1093 List<Profile> activeExternalProfiles = getActiveProfiles(request.getProfiles(), profileActivationContext);
1094
1095 result.setActiveExternalProfiles(activeExternalProfiles);
1096
1097 if (!activeExternalProfiles.isEmpty()) {
1098 Map<String, String> profileProps = new HashMap<>();
1099 for (Profile profile : activeExternalProfiles) {
1100 profileProps.putAll(profile.getProperties());
1101 }
1102 profileProps.putAll(request.getUserProperties());
1103 profileActivationContext.setUserProperties(profileProps);
1104 }
1105
1106 profileActivationContext.setModel(inputModel);
1107 setSource(inputModel);
1108 List<Profile> activePomProfiles = getActiveProfiles(inputModel.getProfiles(), profileActivationContext);
1109
1110
1111 setSource(inputModel);
1112 inputModel = modelNormalizer.mergeDuplicates(inputModel, request, this);
1113
1114 Map<String, Activation> interpolatedActivations = getProfileActivations(inputModel);
1115 inputModel = injectProfileActivations(inputModel, interpolatedActivations);
1116
1117
1118 inputModel = profileInjector.injectProfiles(inputModel, activePomProfiles, request, this);
1119 inputModel = profileInjector.injectProfiles(inputModel, activeExternalProfiles, request, this);
1120
1121 return inputModel;
1122 }
1123
1124 @SuppressWarnings("checkstyle:methodlength")
1125 private Model readEffectiveModel() throws ModelBuilderException {
1126 Model inputModel = readRawModel();
1127 if (hasFatalErrors()) {
1128 throw newModelBuilderException();
1129 }
1130
1131 setRootModel(inputModel);
1132
1133 Model activatedFileModel = activateFileModel(inputModel);
1134
1135
1136 DefaultProfileActivationContext profileActivationContext =
1137 getProfileActivationContext(request, activatedFileModel);
1138
1139 List<Profile> activeExternalProfiles = result.getActiveExternalProfiles();
1140
1141 if (!activeExternalProfiles.isEmpty()) {
1142 Map<String, String> profileProps = new HashMap<>();
1143 for (Profile profile : activeExternalProfiles) {
1144 profileProps.putAll(profile.getProperties());
1145 }
1146 profileProps.putAll(request.getUserProperties());
1147 profileActivationContext.setUserProperties(profileProps);
1148 }
1149
1150 Model parentModel = readParent(activatedFileModel, profileActivationContext);
1151
1152
1153
1154 if (inputModel.getParent() != null && inputModel.getParent().getRelativePath() == null) {
1155 String relPath;
1156 if (parentModel.getPomFile() != null && isBuildRequest()) {
1157 relPath = inputModel
1158 .getPomFile()
1159 .getParent()
1160 .toAbsolutePath()
1161 .relativize(
1162 parentModel.getPomFile().toAbsolutePath().getParent())
1163 .toString();
1164 } else {
1165 relPath = "..";
1166 }
1167 inputModel = inputModel.withParent(inputModel.getParent().withRelativePath(relPath));
1168 }
1169
1170 Model model = inheritanceAssembler.assembleModelInheritance(inputModel, parentModel, request, this);
1171
1172
1173 model = modelNormalizer.mergeDuplicates(model, request, this);
1174
1175
1176 profileActivationContext.setModel(model);
1177
1178
1179 List<Profile> activePomProfiles = getActiveProfiles(model.getProfiles(), profileActivationContext);
1180 model = profileInjector.injectProfiles(model, activePomProfiles, request, this);
1181 model = profileInjector.injectProfiles(model, activeExternalProfiles, request, this);
1182
1183 addActivePomProfiles(activePomProfiles);
1184
1185
1186 Model resultModel = model;
1187 resultModel = interpolateModel(resultModel, request, this);
1188
1189
1190 resultModel = modelUrlNormalizer.normalize(resultModel, request);
1191
1192
1193 if (!resultModel.getRepositories().isEmpty()) {
1194 List<String> oldRepos =
1195 repositories.stream().map(Object::toString).toList();
1196 mergeRepositories(resultModel, true);
1197 List<String> newRepos =
1198 repositories.stream().map(Object::toString).toList();
1199 if (!Objects.equals(oldRepos, newRepos)) {
1200 logger.debug("Replacing repositories from " + resultModel.getId() + "\n"
1201 + newRepos.stream().map(s -> " " + s).collect(Collectors.joining("\n")));
1202 }
1203 }
1204
1205 return resultModel;
1206 }
1207
1208 private void addActivePomProfiles(List<Profile> activePomProfiles) {
1209 if (activePomProfiles != null) {
1210 if (result.getActivePomProfiles() == null) {
1211 result.setActivePomProfiles(new ArrayList<>());
1212 }
1213 result.getActivePomProfiles().addAll(activePomProfiles);
1214 }
1215 }
1216
1217 private List<Profile> getActiveProfiles(
1218 Collection<Profile> interpolatedProfiles, DefaultProfileActivationContext profileActivationContext) {
1219 if (isBuildRequestWithActivation()) {
1220 return profileSelector.getActiveProfiles(interpolatedProfiles, profileActivationContext, this);
1221 } else {
1222 return List.of();
1223 }
1224 }
1225
1226 Model readFileModel() throws ModelBuilderException {
1227 Model model = cache(request.getSource(), FILE, this::doReadFileModel);
1228
1229 result.setFileModel(model);
1230 return model;
1231 }
1232
1233 @SuppressWarnings("checkstyle:methodlength")
1234 Model doReadFileModel() throws ModelBuilderException {
1235 ModelSource modelSource = request.getSource();
1236 Model model;
1237 Path rootDirectory;
1238 setSource(modelSource.getLocation());
1239 logger.debug("Reading file model from " + modelSource.getLocation());
1240 try {
1241 boolean strict = isBuildRequest();
1242 try {
1243 rootDirectory = request.getSession().getRootDirectory();
1244 } catch (IllegalStateException ignore) {
1245 rootDirectory = modelSource.getPath();
1246 while (rootDirectory != null && !Files.isDirectory(rootDirectory)) {
1247 rootDirectory = rootDirectory.getParent();
1248 }
1249 }
1250 try (InputStream is = modelSource.openStream()) {
1251 model = modelProcessor.read(XmlReaderRequest.builder()
1252 .strict(strict)
1253 .location(modelSource.getLocation())
1254 .path(modelSource.getPath())
1255 .rootDirectory(rootDirectory)
1256 .inputStream(is)
1257 .transformer(new InliningTransformer())
1258 .build());
1259 } catch (XmlReaderException e) {
1260 if (!strict) {
1261 throw e;
1262 }
1263 try (InputStream is = modelSource.openStream()) {
1264 model = modelProcessor.read(XmlReaderRequest.builder()
1265 .strict(false)
1266 .location(modelSource.getLocation())
1267 .path(modelSource.getPath())
1268 .rootDirectory(rootDirectory)
1269 .inputStream(is)
1270 .transformer(new InliningTransformer())
1271 .build());
1272 } catch (XmlReaderException ne) {
1273
1274 throw e;
1275 }
1276
1277 add(
1278 Severity.ERROR,
1279 Version.V20,
1280 "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage(),
1281 e);
1282 }
1283
1284 InputLocation loc = model.getLocation("");
1285 InputSource v4src = loc != null ? loc.getSource() : null;
1286 if (v4src != null) {
1287 try {
1288 Field field = InputSource.class.getDeclaredField("modelId");
1289 field.setAccessible(true);
1290 field.set(v4src, ModelProblemUtils.toId(model));
1291 } catch (Throwable t) {
1292
1293 throw new IllegalStateException("Unable to set modelId on InputSource", t);
1294 }
1295 }
1296 } catch (XmlReaderException e) {
1297 add(
1298 Severity.FATAL,
1299 Version.BASE,
1300 "Non-parseable POM " + modelSource.getLocation() + ": " + e.getMessage(),
1301 e);
1302 throw newModelBuilderException();
1303 } catch (IOException e) {
1304 String msg = e.getMessage();
1305 if (msg == null || msg.isEmpty()) {
1306
1307 if (e.getClass().getName().endsWith("MalformedInputException")) {
1308 msg = "Some input bytes do not match the file encoding.";
1309 } else {
1310 msg = e.getClass().getSimpleName();
1311 }
1312 }
1313 add(Severity.FATAL, Version.BASE, "Non-readable POM " + modelSource.getLocation() + ": " + msg, e);
1314 throw newModelBuilderException();
1315 }
1316
1317 if (model.getModelVersion() == null) {
1318 String namespace = model.getNamespaceUri();
1319 if (namespace != null && namespace.startsWith(NAMESPACE_PREFIX)) {
1320 model = model.withModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
1321 }
1322 }
1323
1324 if (isBuildRequest()) {
1325 model = model.withPomFile(modelSource.getPath());
1326
1327 Parent parent = model.getParent();
1328 if (parent != null) {
1329 String groupId = parent.getGroupId();
1330 String artifactId = parent.getArtifactId();
1331 String version = parent.getVersion();
1332 String path = parent.getRelativePath();
1333 if ((groupId == null || artifactId == null || version == null)
1334 && (path == null || !path.isEmpty())) {
1335 Path pomFile = model.getPomFile();
1336 Path relativePath = Paths.get(path != null ? path : "..");
1337 Path pomPath = pomFile.resolveSibling(relativePath).normalize();
1338 if (Files.isDirectory(pomPath)) {
1339 pomPath = modelProcessor.locateExistingPom(pomPath);
1340 }
1341 if (pomPath != null && Files.isRegularFile(pomPath)) {
1342 Model parentModel =
1343 derive(Sources.buildSource(pomPath)).readFileModel();
1344 String parentGroupId = getGroupId(parentModel);
1345 String parentArtifactId = parentModel.getArtifactId();
1346 String parentVersion = getVersion(parentModel);
1347 if ((groupId == null || groupId.equals(parentGroupId))
1348 && (artifactId == null || artifactId.equals(parentArtifactId))
1349 && (version == null || version.equals(parentVersion))) {
1350 model = model.withParent(parent.with()
1351 .groupId(parentGroupId)
1352 .artifactId(parentArtifactId)
1353 .version(parentVersion)
1354 .build());
1355 } else {
1356 mismatchRelativePathAndGA(model, parentGroupId, parentArtifactId);
1357 }
1358 } else {
1359 if (!MODEL_VERSION_4_0_0.equals(model.getModelVersion()) && path != null) {
1360 wrongParentRelativePath(model);
1361 }
1362 }
1363 }
1364 }
1365
1366
1367 if (getSubprojects(model).isEmpty()
1368
1369 && !MODEL_VERSION_4_0_0.equals(model.getModelVersion())
1370
1371
1372 && Type.POM.equals(model.getPackaging())) {
1373 List<String> subprojects = new ArrayList<>();
1374 try (Stream<Path> files = Files.list(model.getProjectDirectory())) {
1375 for (Path f : files.toList()) {
1376 if (Files.isDirectory(f)) {
1377 Path subproject = modelProcessor.locateExistingPom(f);
1378 if (subproject != null) {
1379 subprojects.add(f.getFileName().toString());
1380 }
1381 }
1382 }
1383 if (!subprojects.isEmpty()) {
1384 model = model.withSubprojects(subprojects);
1385 }
1386 } catch (IOException e) {
1387 add(Severity.FATAL, Version.V41, "Error discovering subprojects", e);
1388 }
1389 }
1390
1391
1392
1393
1394 Map<String, String> properties = new HashMap<>();
1395 if (!Objects.equals(rootDirectory, model.getProjectDirectory())) {
1396 Path rootModelPath = modelProcessor.locateExistingPom(rootDirectory);
1397 if (rootModelPath != null) {
1398 Model rootModel =
1399 derive(Sources.buildSource(rootModelPath)).readFileModel();
1400 properties.putAll(rootModel.getProperties());
1401 }
1402 } else {
1403 properties.putAll(model.getProperties());
1404 }
1405 properties.putAll(session.getUserProperties());
1406 model = model.with()
1407 .version(replaceCiFriendlyVersion(properties, model.getVersion()))
1408 .parent(
1409 model.getParent() != null
1410 ? model.getParent()
1411 .withVersion(replaceCiFriendlyVersion(
1412 properties,
1413 model.getParent().getVersion()))
1414 : null)
1415 .build();
1416
1417 Map<String, String> newProps = merge(model.getProperties(), session.getUserProperties());
1418 if (newProps != null) {
1419 model = model.withProperties(newProps);
1420 }
1421 model = model.withProfiles(merge(model.getProfiles(), session.getUserProperties()));
1422 }
1423
1424 for (var transformer : transformers) {
1425 model = transformer.transformFileModel(model);
1426 }
1427
1428 setSource(model);
1429 modelValidator.validateFileModel(
1430 session,
1431 model,
1432 isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL,
1433 this);
1434 InternalSession internalSession = InternalSession.from(session);
1435 if (Features.mavenMaven3Personality(internalSession.getSession().getConfigProperties())
1436 && Objects.equals(ModelBuilder.MODEL_VERSION_4_1_0, model.getModelVersion())) {
1437 add(Severity.FATAL, Version.BASE, "Maven3 mode: no higher model version than 4.0.0 allowed");
1438 }
1439 if (hasFatalErrors()) {
1440 throw newModelBuilderException();
1441 }
1442
1443 return model;
1444 }
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456 List<Profile> merge(List<Profile> profiles, Map<String, String> userProperties) {
1457 List<Profile> result = null;
1458 for (int i = 0; i < profiles.size(); i++) {
1459 Profile profile = profiles.get(i);
1460 Map<String, String> props = merge(profile.getProperties(), userProperties);
1461 if (props != null) {
1462 Profile merged = profile.withProperties(props);
1463 if (result == null) {
1464 result = new ArrayList<>(profiles);
1465 }
1466 result.set(i, merged);
1467 }
1468 }
1469 return result != null ? result : profiles;
1470 }
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482 Map<String, String> merge(Map<String, String> properties, Map<String, String> userProperties) {
1483 Map<String, String> result = null;
1484 for (Map.Entry<String, String> entry : properties.entrySet()) {
1485 String key = entry.getKey();
1486 String value = userProperties.get(key);
1487 if (value != null && !Objects.equals(value, entry.getValue())) {
1488 if (result == null) {
1489 result = new LinkedHashMap<>(properties);
1490 }
1491 result.put(entry.getKey(), value);
1492 }
1493 }
1494 return result;
1495 }
1496
1497 Model readRawModel() throws ModelBuilderException {
1498
1499 readFileModel();
1500 Model model = cache(request.getSource(), RAW, this::doReadRawModel);
1501
1502 result.setRawModel(model);
1503 return model;
1504 }
1505
1506 private Model doReadRawModel() throws ModelBuilderException {
1507 Model rawModel = readFileModel();
1508
1509 if (!MODEL_VERSION_4_0_0.equals(rawModel.getModelVersion()) && isBuildRequest()) {
1510 rawModel = transformFileToRaw(rawModel);
1511 }
1512
1513 for (var transformer : transformers) {
1514 rawModel = transformer.transformRawModel(rawModel);
1515 }
1516
1517 modelValidator.validateRawModel(
1518 session,
1519 rawModel,
1520 isBuildRequest() ? ModelValidator.VALIDATION_LEVEL_STRICT : ModelValidator.VALIDATION_LEVEL_MINIMAL,
1521 this);
1522
1523 if (hasFatalErrors()) {
1524 throw newModelBuilderException();
1525 }
1526
1527 return rawModel;
1528 }
1529
1530
1531
1532
1533 private record ParentModelWithProfiles(Model model, List<Profile> activatedProfiles) {}
1534
1535
1536
1537
1538 Model readAsParentModel(DefaultProfileActivationContext profileActivationContext) throws ModelBuilderException {
1539 Map<DefaultProfileActivationContext.Record, ParentModelWithProfiles> parentsPerContext =
1540 cache(request.getSource(), PARENT, ConcurrentHashMap::new);
1541
1542 for (Map.Entry<DefaultProfileActivationContext.Record, ParentModelWithProfiles> e :
1543 parentsPerContext.entrySet()) {
1544 if (e.getKey().matches(profileActivationContext)) {
1545 ParentModelWithProfiles cached = e.getValue();
1546
1547
1548
1549
1550 if (profileActivationContext.record != null) {
1551 replayRecordIntoContext(e.getKey(), profileActivationContext);
1552 }
1553
1554 addActivePomProfiles(cached.activatedProfiles());
1555 return cached.model();
1556 }
1557 }
1558
1559
1560
1561
1562
1563
1564 DefaultProfileActivationContext ctx = profileActivationContext.start();
1565 ParentModelWithProfiles modelWithProfiles = doReadAsParentModel(ctx);
1566 DefaultProfileActivationContext.Record record = ctx.stop();
1567 replayRecordIntoContext(record, profileActivationContext);
1568
1569 parentsPerContext.put(record, modelWithProfiles);
1570 addActivePomProfiles(modelWithProfiles.activatedProfiles());
1571 return modelWithProfiles.model();
1572 }
1573
1574 private ParentModelWithProfiles doReadAsParentModel(
1575 DefaultProfileActivationContext childProfileActivationContext) throws ModelBuilderException {
1576 Model raw = readRawModel();
1577 Model parentData = readParent(raw, childProfileActivationContext);
1578 Model parent = new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() {
1579 @Override
1580 protected void mergeModel_Modules(
1581 Model.Builder builder,
1582 Model target,
1583 Model source,
1584 boolean sourceDominant,
1585 Map<Object, Object> context) {}
1586
1587 @Override
1588 protected void mergeModel_Subprojects(
1589 Model.Builder builder,
1590 Model target,
1591 Model source,
1592 boolean sourceDominant,
1593 Map<Object, Object> context) {}
1594 })
1595 .assembleModelInheritance(raw, parentData, request, this);
1596
1597
1598
1599
1600
1601
1602
1603
1604 List<Profile> parentActivePomProfiles =
1605 getActiveProfiles(parent.getProfiles(), childProfileActivationContext);
1606
1607
1608 Model injectedParentModel = profileInjector
1609 .injectProfiles(parent, parentActivePomProfiles, request, this)
1610 .withProfiles(List.of());
1611
1612
1613 return new ParentModelWithProfiles(injectedParentModel.withParent(null), parentActivePomProfiles);
1614 }
1615
1616 private Model importDependencyManagement(Model model, Collection<String> importIds) {
1617 DependencyManagement depMgmt = model.getDependencyManagement();
1618
1619 if (depMgmt == null) {
1620 return model;
1621 }
1622
1623 String importing = model.getGroupId() + ':' + model.getArtifactId() + ':' + model.getVersion();
1624
1625 importIds.add(importing);
1626
1627 List<DependencyManagement> importMgmts = null;
1628
1629 List<Dependency> deps = new ArrayList<>(depMgmt.getDependencies());
1630 for (Iterator<Dependency> it = deps.iterator(); it.hasNext(); ) {
1631 Dependency dependency = it.next();
1632
1633 if (!("pom".equals(dependency.getType()) && "import".equals(dependency.getScope()))
1634 || "bom".equals(dependency.getType())) {
1635 continue;
1636 }
1637
1638 it.remove();
1639
1640 DependencyManagement importMgmt = loadDependencyManagement(dependency, importIds);
1641
1642 if (importMgmt != null) {
1643 if (importMgmts == null) {
1644 importMgmts = new ArrayList<>();
1645 }
1646
1647 importMgmts.add(importMgmt);
1648 }
1649 }
1650
1651 importIds.remove(importing);
1652
1653 model = model.withDependencyManagement(
1654 model.getDependencyManagement().withDependencies(deps));
1655
1656 return dependencyManagementImporter.importManagement(model, importMgmts, request, this);
1657 }
1658
1659 private DependencyManagement loadDependencyManagement(Dependency dependency, Collection<String> importIds) {
1660 String groupId = dependency.getGroupId();
1661 String artifactId = dependency.getArtifactId();
1662 String version = dependency.getVersion();
1663
1664 if (groupId == null || groupId.isEmpty()) {
1665 add(
1666 Severity.ERROR,
1667 Version.BASE,
1668 "'dependencyManagement.dependencies.dependency.groupId' for " + dependency.getManagementKey()
1669 + " is missing.",
1670 dependency.getLocation(""));
1671 return null;
1672 }
1673 if (artifactId == null || artifactId.isEmpty()) {
1674 add(
1675 Severity.ERROR,
1676 Version.BASE,
1677 "'dependencyManagement.dependencies.dependency.artifactId' for " + dependency.getManagementKey()
1678 + " is missing.",
1679 dependency.getLocation(""));
1680 return null;
1681 }
1682 if (version == null || version.isEmpty()) {
1683 add(
1684 Severity.ERROR,
1685 Version.BASE,
1686 "'dependencyManagement.dependencies.dependency.version' for " + dependency.getManagementKey()
1687 + " is missing.",
1688 dependency.getLocation(""));
1689 return null;
1690 }
1691
1692 String imported = groupId + ':' + artifactId + ':' + version;
1693
1694 if (importIds.contains(imported)) {
1695 StringBuilder message =
1696 new StringBuilder("The dependencies of type=pom and with scope=import form a cycle: ");
1697 for (String modelId : importIds) {
1698 message.append(modelId).append(" -> ");
1699 }
1700 message.append(imported);
1701 add(Severity.ERROR, Version.BASE, message.toString());
1702 return null;
1703 }
1704
1705 Model importModel = cache(
1706 repositories,
1707 groupId,
1708 artifactId,
1709 version,
1710 null,
1711 IMPORT,
1712 () -> doLoadDependencyManagement(dependency, groupId, artifactId, version, importIds));
1713 DependencyManagement importMgmt = importModel != null ? importModel.getDependencyManagement() : null;
1714 if (importMgmt == null) {
1715 importMgmt = DependencyManagement.newInstance();
1716 }
1717
1718
1719 List<Exclusion> exclusions = dependency.getExclusions();
1720 if (importMgmt != null && !exclusions.isEmpty()) {
1721
1722 List<Dependency> dependencies = importMgmt.getDependencies().stream()
1723 .filter(candidate -> exclusions.stream().noneMatch(exclusion -> match(exclusion, candidate)))
1724 .map(candidate -> addExclusions(candidate, exclusions))
1725 .collect(Collectors.toList());
1726 importMgmt = importMgmt.withDependencies(dependencies);
1727 }
1728
1729 return importMgmt;
1730 }
1731
1732 @SuppressWarnings("checkstyle:parameternumber")
1733 private Model doLoadDependencyManagement(
1734 Dependency dependency,
1735 String groupId,
1736 String artifactId,
1737 String version,
1738 Collection<String> importIds) {
1739 Model importModel;
1740 ModelSource importSource;
1741 try {
1742 importSource = resolveReactorModel(groupId, artifactId, version);
1743 if (importSource == null) {
1744 importSource = modelResolver.resolveModel(
1745 request.getSession(), repositories, dependency, new AtomicReference<>());
1746 }
1747 } catch (ModelBuilderException | ModelResolverException e) {
1748 StringBuilder buffer = new StringBuilder(256);
1749 buffer.append("Non-resolvable import POM");
1750 if (!containsCoordinates(e.getMessage(), groupId, artifactId, version)) {
1751 buffer.append(' ').append(ModelProblemUtils.toId(groupId, artifactId, version));
1752 }
1753 buffer.append(": ").append(e.getMessage());
1754
1755 add(Severity.ERROR, Version.BASE, buffer.toString(), dependency.getLocation(""), e);
1756 return null;
1757 }
1758
1759 Path rootDirectory;
1760 try {
1761 rootDirectory = request.getSession().getRootDirectory();
1762 } catch (IllegalStateException e) {
1763 rootDirectory = null;
1764 }
1765 if (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT && rootDirectory != null) {
1766 Path sourcePath = importSource.getPath();
1767 if (sourcePath != null && sourcePath.startsWith(rootDirectory)) {
1768 add(
1769 Severity.WARNING,
1770 Version.BASE,
1771 "BOM imports from within reactor should be avoided",
1772 dependency.getLocation(""));
1773 }
1774 }
1775
1776 final ModelBuilderResult importResult;
1777 try {
1778 ModelBuilderRequest importRequest = ModelBuilderRequest.builder()
1779 .session(request.getSession())
1780 .requestType(ModelBuilderRequest.RequestType.CONSUMER_DEPENDENCY)
1781 .systemProperties(request.getSystemProperties())
1782 .userProperties(request.getUserProperties())
1783 .source(importSource)
1784 .repositories(repositories)
1785 .build();
1786 ModelBuilderSessionState modelBuilderSession = derive(importRequest);
1787
1788 modelBuilderSession.buildEffectiveModel(importIds);
1789 importResult = modelBuilderSession.result;
1790 } catch (ModelBuilderException e) {
1791 return null;
1792 }
1793
1794 importModel = importResult.getEffectiveModel();
1795
1796 return importModel;
1797 }
1798
1799 ModelSource resolveReactorModel(String groupId, String artifactId, String version)
1800 throws ModelBuilderException {
1801 Set<ModelSource> sources = mappedSources.get(new GAKey(groupId, artifactId));
1802 if (sources != null) {
1803 for (ModelSource source : sources) {
1804 Model model = derive(source).readRawModel();
1805 if (Objects.equals(getVersion(model), version)) {
1806 return source;
1807 }
1808 }
1809
1810 }
1811 return null;
1812 }
1813
1814 private <T> T cache(
1815 List<RemoteRepository> repositories,
1816 String groupId,
1817 String artifactId,
1818 String version,
1819 String classifier,
1820 String tag,
1821 Supplier<T> supplier) {
1822 RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(session, request);
1823 try {
1824 RgavCacheKey r = new RgavCacheKey(
1825 session, trace.mvnTrace(), repositories, groupId, artifactId, version, classifier, tag);
1826 SourceResponse<RgavCacheKey, T> response =
1827 InternalSession.from(session).request(r, tr -> new SourceResponse<>(tr, supplier.get()));
1828 return response.response;
1829 } finally {
1830 RequestTraceHelper.exit(trace);
1831 }
1832 }
1833
1834 private <T> T cache(Source source, String tag, Supplier<T> supplier) throws ModelBuilderException {
1835 RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(session, request);
1836 try {
1837 SourceCacheKey r = new SourceCacheKey(session, trace.mvnTrace(), source, tag);
1838 SourceResponse<SourceCacheKey, T> response =
1839 InternalSession.from(session).request(r, tr -> new SourceResponse<>(tr, supplier.get()));
1840 return response.response;
1841 } finally {
1842 RequestTraceHelper.exit(trace);
1843 }
1844 }
1845
1846 boolean isBuildRequest() {
1847 return request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_PROJECT
1848 || request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_EFFECTIVE
1849 || request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_CONSUMER;
1850 }
1851
1852 boolean isBuildRequestWithActivation() {
1853 return request.getRequestType() != ModelBuilderRequest.RequestType.BUILD_CONSUMER;
1854 }
1855
1856
1857
1858
1859
1860
1861 private void replayRecordIntoContext(
1862 DefaultProfileActivationContext.Record cachedRecord, DefaultProfileActivationContext targetContext) {
1863 if (targetContext.record == null) {
1864 return;
1865 }
1866
1867
1868
1869 DefaultProfileActivationContext.Record targetRecord = targetContext.record;
1870
1871
1872 cachedRecord.usedActiveProfiles.forEach(targetRecord.usedActiveProfiles::putIfAbsent);
1873
1874
1875 cachedRecord.usedInactiveProfiles.forEach(targetRecord.usedInactiveProfiles::putIfAbsent);
1876
1877
1878 cachedRecord.usedSystemProperties.forEach(targetRecord.usedSystemProperties::putIfAbsent);
1879
1880
1881 cachedRecord.usedUserProperties.forEach(targetRecord.usedUserProperties::putIfAbsent);
1882
1883
1884 cachedRecord.usedModelProperties.forEach(targetRecord.usedModelProperties::putIfAbsent);
1885
1886
1887 cachedRecord.usedModelInfos.forEach(targetRecord.usedModelInfos::putIfAbsent);
1888
1889
1890 cachedRecord.usedExists.forEach(targetRecord.usedExists::putIfAbsent);
1891 }
1892 }
1893
1894 @SuppressWarnings("deprecation")
1895 private static List<String> getSubprojects(Model activated) {
1896 List<String> subprojects = activated.getSubprojects();
1897 if (subprojects.isEmpty()) {
1898 subprojects = activated.getModules();
1899 }
1900 return subprojects;
1901 }
1902
1903 @Override
1904 public Model buildRawModel(ModelBuilderRequest request) throws ModelBuilderException {
1905 RequestTraceHelper.ResolverTrace trace = RequestTraceHelper.enter(request.getSession(), request);
1906 try {
1907 ModelBuilderSessionState build = new ModelBuilderSessionState(request);
1908 Model model = build.readRawModel();
1909 if (build.hasErrors()) {
1910 throw build.newModelBuilderException();
1911 }
1912 return model;
1913 } finally {
1914 RequestTraceHelper.exit(trace);
1915 }
1916 }
1917
1918 static String getGroupId(Model model) {
1919 String groupId = model.getGroupId();
1920 if (groupId == null && model.getParent() != null) {
1921 groupId = model.getParent().getGroupId();
1922 }
1923 return groupId;
1924 }
1925
1926 static String getVersion(Model model) {
1927 String version = model.getVersion();
1928 if (version == null && model.getParent() != null) {
1929 version = model.getParent().getVersion();
1930 }
1931 return version;
1932 }
1933
1934 private DefaultProfileActivationContext getProfileActivationContext(ModelBuilderRequest request, Model model) {
1935 return new DefaultProfileActivationContext(
1936 pathTranslator,
1937 rootLocator,
1938 interpolator,
1939 request.getActiveProfileIds(),
1940 request.getInactiveProfileIds(),
1941 request.getSystemProperties(),
1942 request.getUserProperties(),
1943 model);
1944 }
1945
1946 private Map<String, Activation> getProfileActivations(Model model) {
1947 return model.getProfiles().stream()
1948 .filter(p -> p.getActivation() != null)
1949 .collect(Collectors.toMap(Profile::getId, Profile::getActivation));
1950 }
1951
1952 private Model injectProfileActivations(Model model, Map<String, Activation> activations) {
1953 List<Profile> profiles = new ArrayList<>();
1954 boolean modified = false;
1955 for (Profile profile : model.getProfiles()) {
1956 Activation activation = profile.getActivation();
1957 if (activation != null) {
1958
1959 profile = profile.withActivation(activations.get(profile.getId()));
1960 modified = true;
1961 }
1962 profiles.add(profile);
1963 }
1964 return modified ? model.withProfiles(profiles) : model;
1965 }
1966
1967 private Model interpolateModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems) {
1968 Model interpolatedModel =
1969 modelInterpolator.interpolateModel(model, model.getProjectDirectory(), request, problems);
1970 if (interpolatedModel.getParent() != null) {
1971 Map<String, String> map1 = request.getSession().getUserProperties();
1972 Map<String, String> map2 = model.getProperties();
1973 Map<String, String> map3 = request.getSession().getSystemProperties();
1974 UnaryOperator<String> cb = Interpolator.chain(map1::get, map2::get, map3::get);
1975 try {
1976 String interpolated =
1977 interpolator.interpolate(interpolatedModel.getParent().getVersion(), cb);
1978 interpolatedModel = interpolatedModel.withParent(
1979 interpolatedModel.getParent().withVersion(interpolated));
1980 } catch (Exception e) {
1981 problems.add(
1982 Severity.ERROR,
1983 Version.BASE,
1984 "Failed to interpolate field: "
1985 + interpolatedModel.getParent().getVersion()
1986 + " on class: ",
1987 e);
1988 }
1989 }
1990 interpolatedModel = interpolatedModel.withPomFile(model.getPomFile());
1991 return interpolatedModel;
1992 }
1993
1994 private boolean rawChildVersionReferencesParent(String rawChildModelVersion) {
1995 return rawChildModelVersion.equals("${pom.version}")
1996 || rawChildModelVersion.equals("${project.version}")
1997 || rawChildModelVersion.equals("${pom.parent.version}")
1998 || rawChildModelVersion.equals("${project.parent.version}");
1999 }
2000
2001 private Model getSuperModel(String modelVersion) {
2002 return superPomProvider.getSuperPom(modelVersion);
2003 }
2004
2005 private static org.apache.maven.api.model.Dependency addExclusions(
2006 org.apache.maven.api.model.Dependency candidate, List<Exclusion> exclusions) {
2007 return candidate.withExclusions(Stream.concat(candidate.getExclusions().stream(), exclusions.stream())
2008 .toList());
2009 }
2010
2011 private boolean match(Exclusion exclusion, Dependency candidate) {
2012 return match(exclusion.getGroupId(), candidate.getGroupId())
2013 && match(exclusion.getArtifactId(), candidate.getArtifactId());
2014 }
2015
2016 private boolean match(String match, String text) {
2017 return match.equals("*") || match.equals(text);
2018 }
2019
2020 private boolean containsCoordinates(String message, String groupId, String artifactId, String version) {
2021 return message != null
2022 && (groupId == null || message.contains(groupId))
2023 && (artifactId == null || message.contains(artifactId))
2024 && (version == null || message.contains(version));
2025 }
2026
2027 record GAKey(String groupId, String artifactId) {}
2028
2029 public record RgavCacheKey(
2030 Session session,
2031 RequestTrace trace,
2032 List<RemoteRepository> repositories,
2033 String groupId,
2034 String artifactId,
2035 String version,
2036 String classifier,
2037 String tag)
2038 implements Request<Session> {
2039 @Nonnull
2040 @Override
2041 public Session getSession() {
2042 return session;
2043 }
2044
2045 @Nullable
2046 @Override
2047 public RequestTrace getTrace() {
2048 return trace;
2049 }
2050
2051 @Override
2052 public boolean equals(Object o) {
2053 return o instanceof RgavCacheKey that
2054 && Objects.equals(tag, that.tag)
2055 && Objects.equals(groupId, that.groupId)
2056 && Objects.equals(version, that.version)
2057 && Objects.equals(artifactId, that.artifactId)
2058 && Objects.equals(classifier, that.classifier)
2059 && Objects.equals(repositories, that.repositories);
2060 }
2061
2062 @Override
2063 public int hashCode() {
2064 return Objects.hash(repositories, groupId, artifactId, version, classifier, tag);
2065 }
2066
2067 @Override
2068 public String toString() {
2069 StringBuilder sb = new StringBuilder();
2070 sb.append(getClass().getSimpleName()).append("[").append("gav='");
2071 if (groupId != null) {
2072 sb.append(groupId);
2073 }
2074 sb.append(":");
2075 if (artifactId != null) {
2076 sb.append(artifactId);
2077 }
2078 sb.append(":");
2079 if (version != null) {
2080 sb.append(version);
2081 }
2082 sb.append(":");
2083 if (classifier != null) {
2084 sb.append(classifier);
2085 }
2086 sb.append("', tag='");
2087 sb.append(tag);
2088 sb.append("']");
2089 return sb.toString();
2090 }
2091 }
2092
2093 public record SourceCacheKey(Session session, RequestTrace trace, Source source, String tag)
2094 implements Request<Session>, CacheMetadata {
2095 @Nonnull
2096 @Override
2097 public Session getSession() {
2098 return session;
2099 }
2100
2101 @Nullable
2102 @Override
2103 public RequestTrace getTrace() {
2104 return trace;
2105 }
2106
2107 @Override
2108 public CacheRetention getCacheRetention() {
2109 return source instanceof CacheMetadata cacheMetadata ? cacheMetadata.getCacheRetention() : null;
2110 }
2111
2112 @Override
2113 public boolean equals(Object o) {
2114 return o instanceof SourceCacheKey that
2115 && Objects.equals(tag, that.tag)
2116 && Objects.equals(source, that.source);
2117 }
2118
2119 @Override
2120 public int hashCode() {
2121 return Objects.hash(source, tag);
2122 }
2123
2124 @Override
2125 public String toString() {
2126 return getClass().getSimpleName() + "[" + "location=" + source.getLocation() + ", tag=" + tag + ", path="
2127 + source.getPath() + ']';
2128 }
2129 }
2130
2131 public static class SourceResponse<R extends Request<?>, T> implements Result<R> {
2132 private final R request;
2133 private final T response;
2134
2135 SourceResponse(R request, T response) {
2136 this.request = request;
2137 this.response = response;
2138 }
2139
2140 @Nonnull
2141 @Override
2142 public R getRequest() {
2143 return request;
2144 }
2145 }
2146
2147 static class InliningTransformer implements XmlReaderRequest.Transformer {
2148 static final Set<String> CONTEXTS = Set.of(
2149 "groupId",
2150 "artifactId",
2151 "version",
2152 "namespaceUri",
2153 "packaging",
2154 "scope",
2155 "phase",
2156 "layout",
2157 "policy",
2158 "checksumPolicy",
2159 "updatePolicy");
2160
2161 @Override
2162 public String transform(String input, String context) {
2163 return CONTEXTS.contains(context) ? input.intern() : input;
2164 }
2165 }
2166 }