1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.cling.invoker.mvnup.goals;
20
21 import java.io.File;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.util.Comparator;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31
32 import eu.maveniverse.domtrip.Document;
33 import eu.maveniverse.domtrip.Editor;
34 import eu.maveniverse.domtrip.Element;
35 import org.apache.maven.api.RemoteRepository;
36 import org.apache.maven.api.Session;
37 import org.apache.maven.api.cli.mvnup.UpgradeOptions;
38 import org.apache.maven.api.di.Inject;
39 import org.apache.maven.api.di.Named;
40 import org.apache.maven.api.di.Priority;
41 import org.apache.maven.api.di.Singleton;
42 import org.apache.maven.api.model.Build;
43 import org.apache.maven.api.model.Model;
44 import org.apache.maven.api.model.Parent;
45 import org.apache.maven.api.model.Plugin;
46 import org.apache.maven.api.model.PluginManagement;
47 import org.apache.maven.api.model.Repository;
48 import org.apache.maven.api.model.RepositoryPolicy;
49 import org.apache.maven.api.services.ModelBuilder;
50 import org.apache.maven.api.services.ModelBuilderRequest;
51 import org.apache.maven.api.services.ModelBuilderResult;
52 import org.apache.maven.api.services.RepositoryFactory;
53 import org.apache.maven.api.services.Sources;
54 import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
55 import org.apache.maven.impl.standalone.ApiRunner;
56 import org.codehaus.plexus.components.secdispatcher.Dispatcher;
57 import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher;
58 import org.eclipse.aether.internal.impl.DefaultPathProcessor;
59 import org.eclipse.aether.internal.impl.DefaultTransporterProvider;
60 import org.eclipse.aether.internal.impl.transport.http.DefaultChecksumExtractor;
61 import org.eclipse.aether.spi.connector.transport.TransporterProvider;
62 import org.eclipse.aether.transport.file.FileTransporterFactory;
63 import org.eclipse.aether.transport.jdk.JdkTransporterFactory;
64
65 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.ARTIFACT_ID;
66 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.BUILD;
67 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.GROUP_ID;
68 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PARENT;
69 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN;
70 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGINS;
71 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PLUGIN_MANAGEMENT;
72 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.PROPERTIES;
73 import static eu.maveniverse.domtrip.maven.MavenPomElements.Elements.VERSION;
74 import static eu.maveniverse.domtrip.maven.MavenPomElements.Plugins.DEFAULT_MAVEN_PLUGIN_GROUP_ID;
75 import static eu.maveniverse.domtrip.maven.MavenPomElements.Plugins.MAVEN_4_COMPATIBILITY_REASON;
76 import static eu.maveniverse.domtrip.maven.MavenPomElements.Plugins.MAVEN_PLUGIN_PREFIX;
77
78
79
80
81
82 @Named
83 @Singleton
84 @Priority(10)
85 public class PluginUpgradeStrategy extends AbstractUpgradeStrategy {
86
87 private static final List<PluginUpgrade> PLUGIN_UPGRADES = List.of(
88 new PluginUpgrade(
89 DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2.0", MAVEN_4_COMPATIBILITY_REASON),
90 new PluginUpgrade(
91 DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-exec-plugin", "3.2.0", MAVEN_4_COMPATIBILITY_REASON),
92 new PluginUpgrade(
93 DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-enforcer-plugin", "3.0.0", MAVEN_4_COMPATIBILITY_REASON),
94 new PluginUpgrade("org.codehaus.mojo", "flatten-maven-plugin", "1.2.7", MAVEN_4_COMPATIBILITY_REASON),
95 new PluginUpgrade(
96 DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-shade-plugin", "3.5.0", MAVEN_4_COMPATIBILITY_REASON),
97 new PluginUpgrade(
98 DEFAULT_MAVEN_PLUGIN_GROUP_ID,
99 "maven-remote-resources-plugin",
100 "3.0.0",
101 MAVEN_4_COMPATIBILITY_REASON));
102
103 private Session session;
104
105 @Inject
106 public PluginUpgradeStrategy() {}
107
108 @Override
109 public boolean isApplicable(UpgradeContext context) {
110 UpgradeOptions options = getOptions(context);
111 return isOptionEnabled(options, options.plugins(), true);
112 }
113
114 @Override
115 public String getDescription() {
116 return "Upgrading Maven plugins to recommended versions";
117 }
118
119 @Override
120 public UpgradeResult doApply(UpgradeContext context, Map<Path, Document> pomMap) {
121 Set<Path> processedPoms = new HashSet<>();
122 Set<Path> modifiedPoms = new HashSet<>();
123 Set<Path> errorPoms = new HashSet<>();
124
125 try {
126
127 Path tempDir = createTempProjectStructure(context, pomMap);
128
129
130 Map<Path, Set<String>> pluginsNeedingManagement =
131 analyzePluginsUsingEffectiveModels(context, pomMap, tempDir);
132
133
134 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
135 Path pomPath = entry.getKey();
136 Document pomDocument = entry.getValue();
137 processedPoms.add(pomPath);
138
139 context.info(pomPath + " (checking for plugin upgrades)");
140 context.indent();
141
142 try {
143 boolean hasUpgrades = false;
144
145
146 hasUpgrades |= upgradePluginsInDocument(pomDocument, context);
147
148
149
150
151
152 Set<String> pluginsForThisPom = pluginsNeedingManagement.get(pomPath);
153 if (pluginsForThisPom != null && !pluginsForThisPom.isEmpty()) {
154 hasUpgrades |= addPluginManagementForEffectivePlugins(context, pomDocument, pluginsForThisPom);
155 context.detail("Added plugin management to " + pomPath + " (target parent for "
156 + pluginsForThisPom.size() + " plugins)");
157 }
158
159 if (hasUpgrades) {
160 modifiedPoms.add(pomPath);
161 context.success("Plugin upgrades applied");
162 } else {
163 context.success("No plugin upgrades needed");
164 }
165 } catch (Exception e) {
166 context.failure("Failed to upgrade plugins: " + e.getMessage());
167 errorPoms.add(pomPath);
168 } finally {
169 context.unindent();
170 }
171 }
172
173
174 cleanupTempDirectory(tempDir);
175
176 } catch (Exception e) {
177 context.failure("Failed to create temp project structure: " + e.getMessage());
178
179 errorPoms.addAll(pomMap.keySet());
180 }
181
182 return new UpgradeResult(processedPoms, modifiedPoms, errorPoms);
183 }
184
185
186
187
188
189
190 private boolean upgradePluginsInDocument(Document pomDocument, UpgradeContext context) {
191 Element root = pomDocument.root();
192 boolean hasUpgrades = false;
193
194
195 Map<String, PluginUpgradeInfo> pluginUpgrades = getPluginUpgradesMap();
196
197
198 Element buildElement = root.child(BUILD).orElse(null);
199 if (buildElement != null) {
200 Element pluginsElement = buildElement.child(PLUGINS).orElse(null);
201 if (pluginsElement != null) {
202 hasUpgrades |= upgradePluginsInSection(
203 pluginsElement, pluginUpgrades, pomDocument, BUILD + "/" + PLUGINS, context);
204 }
205
206
207 Element pluginManagementElement =
208 buildElement.child(PLUGIN_MANAGEMENT).orElse(null);
209 if (pluginManagementElement != null) {
210 Element managedPluginsElement =
211 pluginManagementElement.child(PLUGINS).orElse(null);
212 if (managedPluginsElement != null) {
213 hasUpgrades |= upgradePluginsInSection(
214 managedPluginsElement,
215 pluginUpgrades,
216 pomDocument,
217 BUILD + "/" + PLUGIN_MANAGEMENT + "/" + PLUGINS,
218 context);
219 }
220 }
221 }
222
223 return hasUpgrades;
224 }
225
226
227
228
229 private Map<String, PluginUpgradeInfo> getPluginUpgradesMap() {
230 Map<String, PluginUpgradeInfo> upgrades = new HashMap<>();
231 upgrades.put(
232 DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-compiler-plugin",
233 new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-compiler-plugin", "3.2"));
234 upgrades.put(
235 "org.codehaus.mojo:exec-maven-plugin",
236 new PluginUpgradeInfo("org.codehaus.mojo", "exec-maven-plugin", "3.2.0"));
237 upgrades.put(
238 DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-enforcer-plugin",
239 new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-enforcer-plugin", "3.0.0"));
240 upgrades.put(
241 "org.codehaus.mojo:flatten-maven-plugin",
242 new PluginUpgradeInfo("org.codehaus.mojo", "flatten-maven-plugin", "1.2.7"));
243 upgrades.put(
244 DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-shade-plugin",
245 new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-shade-plugin", "3.5.0"));
246 upgrades.put(
247 DEFAULT_MAVEN_PLUGIN_GROUP_ID + ":maven-remote-resources-plugin",
248 new PluginUpgradeInfo(DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-remote-resources-plugin", "3.0.0"));
249 return upgrades;
250 }
251
252
253
254
255 private boolean upgradePluginsInSection(
256 Element pluginsElement,
257 Map<String, PluginUpgradeInfo> pluginUpgrades,
258 Document pomDocument,
259 String sectionName,
260 UpgradeContext context) {
261
262 return pluginsElement
263 .children(PLUGIN)
264 .map(pluginElement -> {
265 String groupId = getChildText(pluginElement, GROUP_ID);
266 String artifactId = getChildText(pluginElement, ARTIFACT_ID);
267
268
269 if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
270 groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
271 }
272
273 if (groupId != null && artifactId != null) {
274 String pluginKey = groupId + ":" + artifactId;
275 PluginUpgradeInfo upgrade = pluginUpgrades.get(pluginKey);
276
277 if (upgrade != null) {
278 return upgradePluginVersion(pluginElement, upgrade, pomDocument, sectionName, context);
279 }
280 }
281 return false;
282 })
283 .reduce(false, Boolean::logicalOr);
284 }
285
286
287
288
289 private boolean upgradePluginVersion(
290 Element pluginElement,
291 PluginUpgradeInfo upgrade,
292 Document pomDocument,
293 String sectionName,
294 UpgradeContext context) {
295 Element versionElement = pluginElement.child(VERSION).orElse(null);
296 String currentVersion;
297 boolean isProperty = false;
298 String propertyName = null;
299
300 if (versionElement != null) {
301 currentVersion = versionElement.textContentTrimmed();
302
303 if (currentVersion.startsWith("${") && currentVersion.endsWith("}")) {
304 isProperty = true;
305 propertyName = currentVersion.substring(2, currentVersion.length() - 1);
306 }
307 } else {
308
309 context.debug("Plugin " + upgrade.groupId + ":" + upgrade.artifactId
310 + " has no explicit version, may inherit from parent");
311 return false;
312 }
313
314 if (isProperty) {
315
316 return upgradePropertyVersion(pomDocument, propertyName, upgrade, sectionName, context);
317 } else {
318
319 if (isVersionBelow(currentVersion, upgrade.minVersion)) {
320 Editor editor = new Editor(pomDocument);
321 editor.setTextContent(versionElement, upgrade.minVersion);
322 context.detail("Upgraded " + upgrade.groupId + ":" + upgrade.artifactId + " from " + currentVersion
323 + " to " + upgrade.minVersion + " in " + sectionName);
324 return true;
325 } else {
326 context.debug("Plugin " + upgrade.groupId + ":" + upgrade.artifactId + " version " + currentVersion
327 + " is already >= " + upgrade.minVersion);
328 }
329 }
330
331 return false;
332 }
333
334
335
336
337 private boolean upgradePropertyVersion(
338 Document pomDocument,
339 String propertyName,
340 PluginUpgradeInfo upgrade,
341 String sectionName,
342 UpgradeContext context) {
343 Editor editor = new Editor(pomDocument);
344 Element root = editor.root();
345 Element propertiesElement = root.child(PROPERTIES).orElse(null);
346
347 if (propertiesElement != null) {
348 Element propertyElement = propertiesElement.child(propertyName).orElse(null);
349 if (propertyElement != null) {
350 String currentVersion = propertyElement.textContentTrimmed();
351 if (isVersionBelow(currentVersion, upgrade.minVersion)) {
352 editor.setTextContent(propertyElement, upgrade.minVersion);
353 context.detail("Upgraded property " + propertyName + " (for " + upgrade.groupId
354 + ":"
355 + upgrade.artifactId + ") from " + currentVersion + " to " + upgrade.minVersion
356 + " in "
357 + sectionName);
358 return true;
359 } else {
360 context.debug("Property " + propertyName + " version " + currentVersion + " is already >= "
361 + upgrade.minVersion);
362 }
363 } else {
364 context.warning("Property " + propertyName + " not found in POM properties");
365 }
366 } else {
367 context.warning("No properties section found in POM for property " + propertyName);
368 }
369
370 return false;
371 }
372
373
374
375
376
377 private boolean isVersionBelow(String currentVersion, String minVersion) {
378 if (currentVersion == null || minVersion == null) {
379 return false;
380 }
381
382
383 String cleanCurrent = currentVersion.split("-")[0];
384 String cleanMin = minVersion.split("-")[0];
385
386 try {
387 String[] currentParts = cleanCurrent.split("\\.");
388 String[] minParts = cleanMin.split("\\.");
389
390 int maxLength = Math.max(currentParts.length, minParts.length);
391
392 for (int i = 0; i < maxLength; i++) {
393 int currentPart = i < currentParts.length ? Integer.parseInt(currentParts[i]) : 0;
394 int minPart = i < minParts.length ? Integer.parseInt(minParts[i]) : 0;
395
396 if (currentPart < minPart) {
397 return true;
398 } else if (currentPart > minPart) {
399 return false;
400 }
401 }
402
403 return false;
404 } catch (NumberFormatException e) {
405
406 return currentVersion.compareTo(minVersion) < 0;
407 }
408 }
409
410
411
412
413 private String getChildText(Element parent, String childName) {
414 Element child = parent.child(childName).orElse(null);
415 return child != null ? child.textContentTrimmed() : null;
416 }
417
418
419
420
421 public static List<PluginUpgrade> getPluginUpgrades() {
422 return PLUGIN_UPGRADES;
423 }
424
425
426
427
428 private Session getSession() {
429 if (session == null) {
430 session = createMaven4Session();
431 }
432 return session;
433 }
434
435
436
437
438 private Session createMaven4Session() {
439 Session session = ApiRunner.createSession(injector -> {
440 injector.bindInstance(Dispatcher.class, new LegacyDispatcher());
441
442 injector.bindInstance(
443 TransporterProvider.class,
444 new DefaultTransporterProvider(Map.of(
445 "https",
446 new JdkTransporterFactory(
447 new DefaultChecksumExtractor(Map.of()), new DefaultPathProcessor()),
448 "file",
449 new FileTransporterFactory())));
450 });
451
452
453
454 RemoteRepository central =
455 session.createRemoteRepository(RemoteRepository.CENTRAL_ID, "https://repo.maven.apache.org/maven2");
456 RemoteRepository snapshots = session.getService(RepositoryFactory.class)
457 .createRemote(Repository.newBuilder()
458 .id("apache-snapshots")
459 .url("https://repository.apache.org/content/repositories/snapshots/")
460 .releases(RepositoryPolicy.newBuilder().enabled("false").build())
461 .snapshots(RepositoryPolicy.newBuilder().enabled("true").build())
462 .build());
463
464 return session.withRemoteRepositories(List.of(central, snapshots));
465 }
466
467
468
469
470
471 private Path createTempProjectStructure(UpgradeContext context, Map<Path, Document> pomMap) throws Exception {
472 Path tempDir = Files.createTempDirectory("mvnup-project-");
473 context.debug("Created temp project directory: " + tempDir);
474
475
476 Path commonRoot = findCommonRoot(pomMap.keySet());
477 context.debug("Common root: " + commonRoot);
478
479
480 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
481 Path originalPath = entry.getKey();
482 Document document = entry.getValue();
483
484
485 Path relativePath = commonRoot.relativize(originalPath);
486 Path tempPomPath = tempDir.resolve(relativePath);
487
488
489 Files.createDirectories(tempPomPath.getParent());
490
491
492 writePomToFile(document, tempPomPath);
493 context.debug("Wrote POM to temp location: " + tempPomPath);
494 }
495
496 return tempDir;
497 }
498
499
500
501
502 private Path findCommonRoot(Set<Path> pomPaths) {
503 Path commonRoot = null;
504 for (Path pomPath : pomPaths) {
505 Path parent = pomPath.getParent();
506 if (parent == null) {
507 parent = Path.of(".");
508 }
509 if (commonRoot == null) {
510 commonRoot = parent;
511 } else {
512
513 while (!parent.startsWith(commonRoot)) {
514 commonRoot = commonRoot.getParent();
515 if (commonRoot == null) {
516 break;
517 }
518 }
519 }
520 }
521 return commonRoot;
522 }
523
524
525
526
527 private void writePomToFile(Document document, Path filePath) throws Exception {
528 Files.writeString(filePath, document.toXml());
529 }
530
531
532
533
534
535 private Map<Path, Set<String>> analyzePluginsUsingEffectiveModels(
536 UpgradeContext context, Map<Path, Document> pomMap, Path tempDir) {
537 Map<Path, Set<String>> result = new HashMap<>();
538 Map<String, PluginUpgrade> pluginUpgrades = getPluginUpgradesAsMap();
539
540 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
541 Path originalPomPath = entry.getKey();
542
543 try {
544
545 Path commonRoot = findCommonRoot(pomMap.keySet());
546 Path relativePath = commonRoot.relativize(originalPomPath);
547 Path tempPomPath = tempDir.resolve(relativePath);
548
549
550 Set<String> pluginsNeedingUpgrade =
551 analyzeEffectiveModelForPlugins(context, tempPomPath, pluginUpgrades);
552
553
554 Path targetPomForManagement =
555 findLastLocalParentForPluginManagement(context, tempPomPath, pomMap, tempDir, commonRoot);
556
557 if (targetPomForManagement != null) {
558 result.computeIfAbsent(targetPomForManagement, k -> new HashSet<>())
559 .addAll(pluginsNeedingUpgrade);
560
561 if (!pluginsNeedingUpgrade.isEmpty()) {
562 context.debug("Will add plugin management to " + targetPomForManagement + " for plugins: "
563 + pluginsNeedingUpgrade);
564 }
565 }
566
567 } catch (Exception e) {
568 context.debug("Failed to analyze effective model for " + originalPomPath + ": " + e.getMessage());
569 }
570 }
571
572 return result;
573 }
574
575
576
577
578 private Map<String, PluginUpgrade> getPluginUpgradesAsMap() {
579 return PLUGIN_UPGRADES.stream()
580 .collect(Collectors.toMap(
581 upgrade -> upgrade.groupId() + ":" + upgrade.artifactId(), upgrade -> upgrade));
582 }
583
584
585
586
587 private Set<String> analyzeEffectiveModelForPlugins(
588 UpgradeContext context, Path tempPomPath, Map<String, PluginUpgrade> pluginUpgrades) {
589
590
591 Session session = getSession();
592 ModelBuilder modelBuilder = session.getService(ModelBuilder.class);
593
594
595 ModelBuilderRequest request = ModelBuilderRequest.builder()
596 .session(session)
597 .source(Sources.buildSource(tempPomPath))
598 .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
599 .recursive(false)
600 .build();
601
602 ModelBuilderResult result = modelBuilder.newSession().build(request);
603 Model effectiveModel = result.getEffectiveModel();
604
605
606 return analyzePluginsFromEffectiveModel(context, effectiveModel, pluginUpgrades);
607 }
608
609
610
611
612 private Set<String> analyzePluginsFromEffectiveModel(
613 UpgradeContext context, Model effectiveModel, Map<String, PluginUpgrade> pluginUpgrades) {
614 Set<String> pluginsNeedingUpgrade = new HashSet<>();
615
616 Build build = effectiveModel.getBuild();
617 if (build != null) {
618
619 for (Plugin plugin : build.getPlugins()) {
620 String pluginKey = getPluginKey(plugin);
621 PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
622 if (upgrade != null) {
623 String effectiveVersion = plugin.getVersion();
624 if (isVersionBelow(effectiveVersion, upgrade.minVersion())) {
625 pluginsNeedingUpgrade.add(pluginKey);
626 context.debug("Plugin " + pluginKey + " version " + effectiveVersion + " needs upgrade to "
627 + upgrade.minVersion());
628 }
629 }
630 }
631
632
633 PluginManagement pluginManagement = build.getPluginManagement();
634 if (pluginManagement != null) {
635 for (Plugin plugin : pluginManagement.getPlugins()) {
636 String pluginKey = getPluginKey(plugin);
637 PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
638 if (upgrade != null) {
639 String effectiveVersion = plugin.getVersion();
640 if (isVersionBelow(effectiveVersion, upgrade.minVersion())) {
641 pluginsNeedingUpgrade.add(pluginKey);
642 context.debug("Managed plugin " + pluginKey + " version " + effectiveVersion
643 + " needs upgrade to " + upgrade.minVersion());
644 }
645 }
646 }
647 }
648 }
649
650 return pluginsNeedingUpgrade;
651 }
652
653
654
655
656 private String getPluginKey(Plugin plugin) {
657 String groupId = plugin.getGroupId();
658 String artifactId = plugin.getArtifactId();
659
660
661 if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
662 groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
663 }
664
665 return groupId + ":" + artifactId;
666 }
667
668
669
670
671
672
673 private Path findLastLocalParentForPluginManagement(
674 UpgradeContext context, Path tempPomPath, Map<Path, Document> pomMap, Path tempDir, Path commonRoot) {
675
676
677 Session session = getSession();
678 ModelBuilder modelBuilder = session.getService(ModelBuilder.class);
679
680 ModelBuilderRequest request = ModelBuilderRequest.builder()
681 .session(session)
682 .source(Sources.buildSource(tempPomPath))
683 .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
684 .recursive(false)
685 .build();
686
687 ModelBuilderResult result = modelBuilder.newSession().build(request);
688 Model effectiveModel = result.getEffectiveModel();
689
690
691 Path relativePath = tempDir.relativize(tempPomPath);
692 Path currentOriginalPath = commonRoot.resolve(relativePath);
693
694
695 Path lastLocalParent = currentOriginalPath;
696
697
698 Model currentModel = effectiveModel;
699 while (currentModel.getParent() != null) {
700 Parent parent = currentModel.getParent();
701
702
703 Path parentPath = findParentInPomMap(parent, pomMap);
704 if (parentPath != null) {
705
706 lastLocalParent = parentPath;
707
708
709 Path parentTempPath = tempDir.resolve(commonRoot.relativize(parentPath));
710 ModelBuilderRequest parentRequest = ModelBuilderRequest.builder()
711 .session(session)
712 .source(Sources.buildSource(parentTempPath))
713 .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
714 .recursive(false)
715 .build();
716
717 ModelBuilderResult parentResult = modelBuilder.newSession().build(parentRequest);
718 currentModel = parentResult.getEffectiveModel();
719 } else {
720
721 break;
722 }
723 }
724
725 context.debug("Last local parent for " + currentOriginalPath + " is " + lastLocalParent);
726 return lastLocalParent;
727 }
728
729
730
731
732 private Path findParentInPomMap(Parent parent, Map<Path, Document> pomMap) {
733 String parentGroupId = parent.getGroupId();
734 String parentArtifactId = parent.getArtifactId();
735 String parentVersion = parent.getVersion();
736
737 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
738 Document doc = entry.getValue();
739 Element root = doc.root();
740
741
742 String groupId = getChildText(root, GROUP_ID);
743 String artifactId = getChildText(root, ARTIFACT_ID);
744 String version = getChildText(root, VERSION);
745
746
747 Element parentElement = root.child(PARENT).orElse(null);
748 if (parentElement != null) {
749 if (groupId == null) {
750 groupId = getChildText(parentElement, GROUP_ID);
751 }
752 if (version == null) {
753 version = getChildText(parentElement, VERSION);
754 }
755 }
756
757
758 if (parentGroupId.equals(groupId) && parentArtifactId.equals(artifactId) && parentVersion.equals(version)) {
759 return entry.getKey();
760 }
761 }
762
763 return null;
764 }
765
766
767
768
769 private boolean addPluginManagementForEffectivePlugins(
770 UpgradeContext context, Document pomDocument, Set<String> pluginKeys) {
771
772 Map<String, PluginUpgrade> pluginUpgrades = getPluginUpgradesAsMap();
773 boolean hasUpgrades = false;
774
775 Element root = pomDocument.root();
776
777
778 Element buildElement = root.child(BUILD).orElse(null);
779 if (buildElement == null) {
780 buildElement = DomUtils.insertNewElement(BUILD, root);
781 }
782
783 Element pluginManagementElement = buildElement.child(PLUGIN_MANAGEMENT).orElse(null);
784 if (pluginManagementElement == null) {
785 pluginManagementElement = DomUtils.insertNewElement(PLUGIN_MANAGEMENT, buildElement);
786 }
787
788 Element managedPluginsElement = pluginManagementElement.child(PLUGINS).orElse(null);
789 if (managedPluginsElement == null) {
790 managedPluginsElement = DomUtils.insertNewElement(PLUGINS, pluginManagementElement);
791 }
792
793
794 for (String pluginKey : pluginKeys) {
795 PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
796 if (upgrade != null) {
797
798 if (!isPluginAlreadyManagedInElement(managedPluginsElement, upgrade)) {
799 addPluginManagementEntryFromUpgrade(managedPluginsElement, upgrade, context);
800 hasUpgrades = true;
801 }
802 }
803 }
804
805 return hasUpgrades;
806 }
807
808
809
810
811 private boolean isPluginAlreadyManagedInElement(Element pluginsElement, PluginUpgrade upgrade) {
812 List<Element> pluginElements = pluginsElement.children(PLUGIN).toList();
813 for (Element pluginElement : pluginElements) {
814 String groupId = getChildText(pluginElement, GROUP_ID);
815 String artifactId = getChildText(pluginElement, ARTIFACT_ID);
816
817
818 if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
819 groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
820 }
821
822 if (upgrade.groupId().equals(groupId) && upgrade.artifactId().equals(artifactId)) {
823 return true;
824 }
825 }
826 return false;
827 }
828
829
830
831
832 private void addPluginManagementEntryFromUpgrade(
833 Element managedPluginsElement, PluginUpgrade upgrade, UpgradeContext context) {
834
835 DomUtils.createPlugin(managedPluginsElement, upgrade.groupId(), upgrade.artifactId(), upgrade.minVersion());
836
837 context.detail("Added plugin management for " + upgrade.groupId() + ":" + upgrade.artifactId() + " version "
838 + upgrade.minVersion() + " (found through effective model analysis)");
839 }
840
841
842
843
844 private void cleanupTempDirectory(Path tempDir) {
845 try {
846 Files.walk(tempDir)
847 .sorted(Comparator.reverseOrder())
848 .map(Path::toFile)
849 .forEach(File::delete);
850 } catch (Exception e) {
851
852 }
853 }
854
855
856
857
858
859
860 public static class PluginUpgradeInfo {
861
862 final String groupId;
863
864
865 final String artifactId;
866
867
868 final String minVersion;
869
870
871
872
873
874
875
876
877 PluginUpgradeInfo(String groupId, String artifactId, String minVersion) {
878 this.groupId = groupId;
879 this.artifactId = artifactId;
880 this.minVersion = minVersion;
881 }
882 }
883 }