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.io.FileWriter;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31
32 import org.apache.maven.api.RemoteRepository;
33 import org.apache.maven.api.Session;
34 import org.apache.maven.api.cli.mvnup.UpgradeOptions;
35 import org.apache.maven.api.di.Inject;
36 import org.apache.maven.api.di.Named;
37 import org.apache.maven.api.di.Priority;
38 import org.apache.maven.api.di.Singleton;
39 import org.apache.maven.api.model.Build;
40 import org.apache.maven.api.model.Model;
41 import org.apache.maven.api.model.Parent;
42 import org.apache.maven.api.model.Plugin;
43 import org.apache.maven.api.model.PluginManagement;
44 import org.apache.maven.api.model.Repository;
45 import org.apache.maven.api.model.RepositoryPolicy;
46 import org.apache.maven.api.services.ModelBuilder;
47 import org.apache.maven.api.services.ModelBuilderRequest;
48 import org.apache.maven.api.services.ModelBuilderResult;
49 import org.apache.maven.api.services.RepositoryFactory;
50 import org.apache.maven.api.services.Sources;
51 import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
52 import org.apache.maven.impl.standalone.ApiRunner;
53 import org.codehaus.plexus.components.secdispatcher.Dispatcher;
54 import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher;
55 import org.eclipse.aether.internal.impl.DefaultPathProcessor;
56 import org.eclipse.aether.internal.impl.DefaultTransporterProvider;
57 import org.eclipse.aether.internal.impl.transport.http.DefaultChecksumExtractor;
58 import org.eclipse.aether.spi.connector.transport.TransporterProvider;
59 import org.eclipse.aether.transport.file.FileTransporterFactory;
60 import org.eclipse.aether.transport.jdk.JdkTransporterFactory;
61 import org.jdom2.Document;
62 import org.jdom2.Element;
63 import org.jdom2.Namespace;
64 import org.jdom2.output.XMLOutputter;
65
66 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Plugins.DEFAULT_MAVEN_PLUGIN_GROUP_ID;
67 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Plugins.MAVEN_4_COMPATIBILITY_REASON;
68 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.Plugins.MAVEN_PLUGIN_PREFIX;
69 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.ARTIFACT_ID;
70 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.BUILD;
71 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.GROUP_ID;
72 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PARENT;
73 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGIN;
74 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGINS;
75 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.PLUGIN_MANAGEMENT;
76 import static org.apache.maven.cling.invoker.mvnup.goals.UpgradeConstants.XmlElements.VERSION;
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.getRootElement();
192 Namespace namespace = root.getNamespace();
193 boolean hasUpgrades = false;
194
195
196 Map<String, PluginUpgradeInfo> pluginUpgrades = getPluginUpgradesMap();
197
198
199 Element buildElement = root.getChild(UpgradeConstants.XmlElements.BUILD, namespace);
200 if (buildElement != null) {
201 Element pluginsElement = buildElement.getChild(PLUGINS, namespace);
202 if (pluginsElement != null) {
203 hasUpgrades |= upgradePluginsInSection(
204 pluginsElement, namespace, pluginUpgrades, pomDocument, BUILD + "/" + PLUGINS, context);
205 }
206
207
208 Element pluginManagementElement = buildElement.getChild(PLUGIN_MANAGEMENT, namespace);
209 if (pluginManagementElement != null) {
210 Element managedPluginsElement = pluginManagementElement.getChild(PLUGINS, namespace);
211 if (managedPluginsElement != null) {
212 hasUpgrades |= upgradePluginsInSection(
213 managedPluginsElement,
214 namespace,
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 Namespace namespace,
258 Map<String, PluginUpgradeInfo> pluginUpgrades,
259 Document pomDocument,
260 String sectionName,
261 UpgradeContext context) {
262 boolean hasUpgrades = false;
263 List<Element> pluginElements = pluginsElement.getChildren(PLUGIN, namespace);
264
265 for (Element pluginElement : pluginElements) {
266 String groupId = getChildText(pluginElement, GROUP_ID, namespace);
267 String artifactId = getChildText(pluginElement, ARTIFACT_ID, namespace);
268
269
270 if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
271 groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
272 }
273
274 if (groupId != null && artifactId != null) {
275 String pluginKey = groupId + ":" + artifactId;
276 PluginUpgradeInfo upgrade = pluginUpgrades.get(pluginKey);
277
278 if (upgrade != null) {
279 if (upgradePluginVersion(pluginElement, namespace, upgrade, pomDocument, sectionName, context)) {
280 hasUpgrades = true;
281 }
282 }
283 }
284 }
285
286 return hasUpgrades;
287 }
288
289
290
291
292 private boolean upgradePluginVersion(
293 Element pluginElement,
294 Namespace namespace,
295 PluginUpgradeInfo upgrade,
296 Document pomDocument,
297 String sectionName,
298 UpgradeContext context) {
299 Element versionElement = pluginElement.getChild(VERSION, namespace);
300 String currentVersion;
301 boolean isProperty = false;
302 String propertyName = null;
303
304 if (versionElement != null) {
305 currentVersion = versionElement.getTextTrim();
306
307 if (currentVersion.startsWith("${") && currentVersion.endsWith("}")) {
308 isProperty = true;
309 propertyName = currentVersion.substring(2, currentVersion.length() - 1);
310 }
311 } else {
312
313 context.debug("Plugin " + upgrade.groupId + ":" + upgrade.artifactId
314 + " has no explicit version, may inherit from parent");
315 return false;
316 }
317
318 if (isProperty) {
319
320 return upgradePropertyVersion(pomDocument, propertyName, upgrade, sectionName, context);
321 } else {
322
323 if (isVersionBelow(currentVersion, upgrade.minVersion)) {
324 versionElement.setText(upgrade.minVersion);
325 context.detail("Upgraded " + upgrade.groupId + ":" + upgrade.artifactId + " from " + currentVersion
326 + " to " + upgrade.minVersion + " in " + sectionName);
327 return true;
328 } else {
329 context.debug("Plugin " + upgrade.groupId + ":" + upgrade.artifactId + " version " + currentVersion
330 + " is already >= " + upgrade.minVersion);
331 }
332 }
333
334 return false;
335 }
336
337
338
339
340 private boolean upgradePropertyVersion(
341 Document pomDocument,
342 String propertyName,
343 PluginUpgradeInfo upgrade,
344 String sectionName,
345 UpgradeContext context) {
346 Element root = pomDocument.getRootElement();
347 Namespace namespace = root.getNamespace();
348 Element propertiesElement = root.getChild(UpgradeConstants.XmlElements.PROPERTIES, namespace);
349
350 if (propertiesElement != null) {
351 Element propertyElement = propertiesElement.getChild(propertyName, namespace);
352 if (propertyElement != null) {
353 String currentVersion = propertyElement.getTextTrim();
354 if (isVersionBelow(currentVersion, upgrade.minVersion)) {
355 propertyElement.setText(upgrade.minVersion);
356 context.detail("Upgraded property " + propertyName + " (for " + upgrade.groupId
357 + ":"
358 + upgrade.artifactId + ") from " + currentVersion + " to " + upgrade.minVersion
359 + " in "
360 + sectionName);
361 return true;
362 } else {
363 context.debug("Property " + propertyName + " version " + currentVersion + " is already >= "
364 + upgrade.minVersion);
365 }
366 } else {
367 context.warning("Property " + propertyName + " not found in POM properties");
368 }
369 } else {
370 context.warning("No properties section found in POM for property " + propertyName);
371 }
372
373 return false;
374 }
375
376
377
378
379
380 private boolean isVersionBelow(String currentVersion, String minVersion) {
381 if (currentVersion == null || minVersion == null) {
382 return false;
383 }
384
385
386 String cleanCurrent = currentVersion.split("-")[0];
387 String cleanMin = minVersion.split("-")[0];
388
389 try {
390 String[] currentParts = cleanCurrent.split("\\.");
391 String[] minParts = cleanMin.split("\\.");
392
393 int maxLength = Math.max(currentParts.length, minParts.length);
394
395 for (int i = 0; i < maxLength; i++) {
396 int currentPart = i < currentParts.length ? Integer.parseInt(currentParts[i]) : 0;
397 int minPart = i < minParts.length ? Integer.parseInt(minParts[i]) : 0;
398
399 if (currentPart < minPart) {
400 return true;
401 } else if (currentPart > minPart) {
402 return false;
403 }
404 }
405
406 return false;
407 } catch (NumberFormatException e) {
408
409 return currentVersion.compareTo(minVersion) < 0;
410 }
411 }
412
413
414
415
416 private String getChildText(Element parent, String childName, Namespace namespace) {
417 Element child = parent.getChild(childName, namespace);
418 return child != null ? child.getTextTrim() : null;
419 }
420
421
422
423
424 public static List<PluginUpgrade> getPluginUpgrades() {
425 return PLUGIN_UPGRADES;
426 }
427
428
429
430
431 private Session getSession() {
432 if (session == null) {
433 session = createMaven4Session();
434 }
435 return session;
436 }
437
438
439
440
441 private Session createMaven4Session() {
442 Session session = ApiRunner.createSession(injector -> {
443 injector.bindInstance(Dispatcher.class, new LegacyDispatcher());
444
445 injector.bindInstance(
446 TransporterProvider.class,
447 new DefaultTransporterProvider(Map.of(
448 "https",
449 new JdkTransporterFactory(
450 new DefaultChecksumExtractor(Map.of()), new DefaultPathProcessor()),
451 "file",
452 new FileTransporterFactory())));
453 });
454
455
456
457 RemoteRepository central =
458 session.createRemoteRepository(RemoteRepository.CENTRAL_ID, "https://repo.maven.apache.org/maven2");
459 RemoteRepository snapshots = session.getService(RepositoryFactory.class)
460 .createRemote(Repository.newBuilder()
461 .id("apache-snapshots")
462 .url("https://repository.apache.org/content/repositories/snapshots/")
463 .releases(RepositoryPolicy.newBuilder().enabled("false").build())
464 .snapshots(RepositoryPolicy.newBuilder().enabled("true").build())
465 .build());
466
467 return session.withRemoteRepositories(List.of(central, snapshots));
468 }
469
470
471
472
473
474 private Path createTempProjectStructure(UpgradeContext context, Map<Path, Document> pomMap) throws Exception {
475 Path tempDir = Files.createTempDirectory("mvnup-project-");
476 context.debug("Created temp project directory: " + tempDir);
477
478
479 Path commonRoot = findCommonRoot(pomMap.keySet());
480 context.debug("Common root: " + commonRoot);
481
482
483 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
484 Path originalPath = entry.getKey();
485 Document document = entry.getValue();
486
487
488 Path relativePath = commonRoot.relativize(originalPath);
489 Path tempPomPath = tempDir.resolve(relativePath);
490
491
492 Files.createDirectories(tempPomPath.getParent());
493
494
495 writePomToFile(document, tempPomPath);
496 context.debug("Wrote POM to temp location: " + tempPomPath);
497 }
498
499 return tempDir;
500 }
501
502
503
504
505 private Path findCommonRoot(Set<Path> pomPaths) {
506 Path commonRoot = null;
507 for (Path pomPath : pomPaths) {
508 Path parent = pomPath.getParent();
509 if (parent == null) {
510 parent = Path.of(".");
511 }
512 if (commonRoot == null) {
513 commonRoot = parent;
514 } else {
515
516 while (!parent.startsWith(commonRoot)) {
517 commonRoot = commonRoot.getParent();
518 if (commonRoot == null) {
519 break;
520 }
521 }
522 }
523 }
524 return commonRoot;
525 }
526
527
528
529
530 private void writePomToFile(Document document, Path filePath) throws Exception {
531 try (FileWriter writer = new FileWriter(filePath.toFile())) {
532 XMLOutputter outputter = new XMLOutputter();
533 outputter.output(document, writer);
534 }
535 }
536
537
538
539
540
541 private Map<Path, Set<String>> analyzePluginsUsingEffectiveModels(
542 UpgradeContext context, Map<Path, Document> pomMap, Path tempDir) {
543 Map<Path, Set<String>> result = new HashMap<>();
544 Map<String, PluginUpgrade> pluginUpgrades = getPluginUpgradesAsMap();
545
546 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
547 Path originalPomPath = entry.getKey();
548
549 try {
550
551 Path commonRoot = findCommonRoot(pomMap.keySet());
552 Path relativePath = commonRoot.relativize(originalPomPath);
553 Path tempPomPath = tempDir.resolve(relativePath);
554
555
556 Set<String> pluginsNeedingUpgrade =
557 analyzeEffectiveModelForPlugins(context, tempPomPath, pluginUpgrades);
558
559
560 Path targetPomForManagement =
561 findLastLocalParentForPluginManagement(context, tempPomPath, pomMap, tempDir, commonRoot);
562
563 if (targetPomForManagement != null) {
564 result.computeIfAbsent(targetPomForManagement, k -> new HashSet<>())
565 .addAll(pluginsNeedingUpgrade);
566
567 if (!pluginsNeedingUpgrade.isEmpty()) {
568 context.debug("Will add plugin management to " + targetPomForManagement + " for plugins: "
569 + pluginsNeedingUpgrade);
570 }
571 }
572
573 } catch (Exception e) {
574 context.debug("Failed to analyze effective model for " + originalPomPath + ": " + e.getMessage());
575 }
576 }
577
578 return result;
579 }
580
581
582
583
584 private Map<String, PluginUpgrade> getPluginUpgradesAsMap() {
585 Map<String, PluginUpgrade> result = new HashMap<>();
586 for (PluginUpgrade upgrade : PLUGIN_UPGRADES) {
587 String key = upgrade.groupId() + ":" + upgrade.artifactId();
588 result.put(key, upgrade);
589 }
590 return result;
591 }
592
593
594
595
596 private Set<String> analyzeEffectiveModelForPlugins(
597 UpgradeContext context, Path tempPomPath, Map<String, PluginUpgrade> pluginUpgrades) {
598
599
600 Session session = getSession();
601 ModelBuilder modelBuilder = session.getService(ModelBuilder.class);
602
603
604 ModelBuilderRequest request = ModelBuilderRequest.builder()
605 .session(session)
606 .source(Sources.buildSource(tempPomPath))
607 .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
608 .recursive(false)
609 .build();
610
611 ModelBuilderResult result = modelBuilder.newSession().build(request);
612 Model effectiveModel = result.getEffectiveModel();
613
614
615 return analyzePluginsFromEffectiveModel(context, effectiveModel, pluginUpgrades);
616 }
617
618
619
620
621 private Set<String> analyzePluginsFromEffectiveModel(
622 UpgradeContext context, Model effectiveModel, Map<String, PluginUpgrade> pluginUpgrades) {
623 Set<String> pluginsNeedingUpgrade = new HashSet<>();
624
625 Build build = effectiveModel.getBuild();
626 if (build != null) {
627
628 for (Plugin plugin : build.getPlugins()) {
629 String pluginKey = getPluginKey(plugin);
630 PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
631 if (upgrade != null) {
632 String effectiveVersion = plugin.getVersion();
633 if (isVersionBelow(effectiveVersion, upgrade.minVersion())) {
634 pluginsNeedingUpgrade.add(pluginKey);
635 context.debug("Plugin " + pluginKey + " version " + effectiveVersion + " needs upgrade to "
636 + upgrade.minVersion());
637 }
638 }
639 }
640
641
642 PluginManagement pluginManagement = build.getPluginManagement();
643 if (pluginManagement != null) {
644 for (Plugin plugin : pluginManagement.getPlugins()) {
645 String pluginKey = getPluginKey(plugin);
646 PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
647 if (upgrade != null) {
648 String effectiveVersion = plugin.getVersion();
649 if (isVersionBelow(effectiveVersion, upgrade.minVersion())) {
650 pluginsNeedingUpgrade.add(pluginKey);
651 context.debug("Managed plugin " + pluginKey + " version " + effectiveVersion
652 + " needs upgrade to " + upgrade.minVersion());
653 }
654 }
655 }
656 }
657 }
658
659 return pluginsNeedingUpgrade;
660 }
661
662
663
664
665 private String getPluginKey(Plugin plugin) {
666 String groupId = plugin.getGroupId();
667 String artifactId = plugin.getArtifactId();
668
669
670 if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
671 groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
672 }
673
674 return groupId + ":" + artifactId;
675 }
676
677
678
679
680
681
682 private Path findLastLocalParentForPluginManagement(
683 UpgradeContext context, Path tempPomPath, Map<Path, Document> pomMap, Path tempDir, Path commonRoot) {
684
685
686 Session session = getSession();
687 ModelBuilder modelBuilder = session.getService(ModelBuilder.class);
688
689 ModelBuilderRequest request = ModelBuilderRequest.builder()
690 .session(session)
691 .source(Sources.buildSource(tempPomPath))
692 .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
693 .recursive(false)
694 .build();
695
696 ModelBuilderResult result = modelBuilder.newSession().build(request);
697 Model effectiveModel = result.getEffectiveModel();
698
699
700 Path relativePath = tempDir.relativize(tempPomPath);
701 Path currentOriginalPath = commonRoot.resolve(relativePath);
702
703
704 Path lastLocalParent = currentOriginalPath;
705
706
707 Model currentModel = effectiveModel;
708 while (currentModel.getParent() != null) {
709 Parent parent = currentModel.getParent();
710
711
712 Path parentPath = findParentInPomMap(parent, pomMap);
713 if (parentPath != null) {
714
715 lastLocalParent = parentPath;
716
717
718 Path parentTempPath = tempDir.resolve(commonRoot.relativize(parentPath));
719 ModelBuilderRequest parentRequest = ModelBuilderRequest.builder()
720 .session(session)
721 .source(Sources.buildSource(parentTempPath))
722 .requestType(ModelBuilderRequest.RequestType.BUILD_EFFECTIVE)
723 .recursive(false)
724 .build();
725
726 ModelBuilderResult parentResult = modelBuilder.newSession().build(parentRequest);
727 currentModel = parentResult.getEffectiveModel();
728 } else {
729
730 break;
731 }
732 }
733
734 context.debug("Last local parent for " + currentOriginalPath + " is " + lastLocalParent);
735 return lastLocalParent;
736 }
737
738
739
740
741 private Path findParentInPomMap(Parent parent, Map<Path, Document> pomMap) {
742 String parentGroupId = parent.getGroupId();
743 String parentArtifactId = parent.getArtifactId();
744 String parentVersion = parent.getVersion();
745
746 for (Map.Entry<Path, Document> entry : pomMap.entrySet()) {
747 Document doc = entry.getValue();
748 Element root = doc.getRootElement();
749 Namespace namespace = root.getNamespace();
750
751
752 String groupId = getChildText(root, GROUP_ID, namespace);
753 String artifactId = getChildText(root, ARTIFACT_ID, namespace);
754 String version = getChildText(root, VERSION, namespace);
755
756
757 Element parentElement = root.getChild(PARENT, namespace);
758 if (parentElement != null) {
759 if (groupId == null) {
760 groupId = getChildText(parentElement, GROUP_ID, namespace);
761 }
762 if (version == null) {
763 version = getChildText(parentElement, VERSION, namespace);
764 }
765 }
766
767
768 if (parentGroupId.equals(groupId) && parentArtifactId.equals(artifactId) && parentVersion.equals(version)) {
769 return entry.getKey();
770 }
771 }
772
773 return null;
774 }
775
776
777
778
779 private boolean addPluginManagementForEffectivePlugins(
780 UpgradeContext context, Document pomDocument, Set<String> pluginKeys) {
781
782 Map<String, PluginUpgrade> pluginUpgrades = getPluginUpgradesAsMap();
783 boolean hasUpgrades = false;
784
785 Element root = pomDocument.getRootElement();
786 Namespace namespace = root.getNamespace();
787
788
789 Element buildElement = root.getChild(BUILD, namespace);
790 if (buildElement == null) {
791 buildElement = JDomUtils.insertNewElement(BUILD, root);
792 }
793
794 Element pluginManagementElement = buildElement.getChild(PLUGIN_MANAGEMENT, namespace);
795 if (pluginManagementElement == null) {
796 pluginManagementElement = JDomUtils.insertNewElement(PLUGIN_MANAGEMENT, buildElement);
797 }
798
799 Element managedPluginsElement = pluginManagementElement.getChild(PLUGINS, namespace);
800 if (managedPluginsElement == null) {
801 managedPluginsElement = JDomUtils.insertNewElement(PLUGINS, pluginManagementElement);
802 }
803
804
805 for (String pluginKey : pluginKeys) {
806 PluginUpgrade upgrade = pluginUpgrades.get(pluginKey);
807 if (upgrade != null) {
808
809 if (!isPluginAlreadyManagedInElement(managedPluginsElement, namespace, upgrade)) {
810 addPluginManagementEntryFromUpgrade(managedPluginsElement, upgrade, context);
811 hasUpgrades = true;
812 }
813 }
814 }
815
816 return hasUpgrades;
817 }
818
819
820
821
822 private boolean isPluginAlreadyManagedInElement(
823 Element pluginsElement, Namespace namespace, PluginUpgrade upgrade) {
824 List<Element> pluginElements = pluginsElement.getChildren(PLUGIN, namespace);
825 for (Element pluginElement : pluginElements) {
826 String groupId = getChildText(pluginElement, GROUP_ID, namespace);
827 String artifactId = getChildText(pluginElement, ARTIFACT_ID, namespace);
828
829
830 if (groupId == null && artifactId != null && artifactId.startsWith(MAVEN_PLUGIN_PREFIX)) {
831 groupId = DEFAULT_MAVEN_PLUGIN_GROUP_ID;
832 }
833
834 if (upgrade.groupId().equals(groupId) && upgrade.artifactId().equals(artifactId)) {
835 return true;
836 }
837 }
838 return false;
839 }
840
841
842
843
844 private void addPluginManagementEntryFromUpgrade(
845 Element managedPluginsElement, PluginUpgrade upgrade, UpgradeContext context) {
846
847 Element pluginElement = JDomUtils.insertNewElement(PLUGIN, managedPluginsElement);
848
849
850 JDomUtils.insertContentElement(pluginElement, GROUP_ID, upgrade.groupId());
851 JDomUtils.insertContentElement(pluginElement, ARTIFACT_ID, upgrade.artifactId());
852 JDomUtils.insertContentElement(pluginElement, VERSION, upgrade.minVersion());
853
854 context.detail("Added plugin management for " + upgrade.groupId() + ":" + upgrade.artifactId() + " version "
855 + upgrade.minVersion() + " (found through effective model analysis)");
856 }
857
858
859
860
861 private void cleanupTempDirectory(Path tempDir) {
862 try {
863 Files.walk(tempDir)
864 .sorted(Comparator.reverseOrder())
865 .map(Path::toFile)
866 .forEach(File::delete);
867 } catch (Exception e) {
868
869 }
870 }
871
872
873
874
875
876
877 public static class PluginUpgradeInfo {
878
879 final String groupId;
880
881
882 final String artifactId;
883
884
885 final String minVersion;
886
887
888
889
890
891
892
893
894 PluginUpgradeInfo(String groupId, String artifactId, String minVersion) {
895 this.groupId = groupId;
896 this.artifactId = artifactId;
897 this.minVersion = minVersion;
898 }
899 }
900 }