This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch mvnup in repository https://gitbox.apache.org/repos/asf/maven.git
commit f8792df3411113eba798f2644852ffcad3a06e29 Author: Guillaume Nodet <gno...@gmail.com> AuthorDate: Fri May 30 00:18:36 2025 +0200 Remove duplicate headers --- .../cling/invoker/mvnup/goals/BaseUpgradeGoal.java | 4 + .../invoker/mvnup/goals/ParentPomResolver.java | 279 +++++++++++++++++++++ .../invoker/mvnup/jdom/JDomContentHelper.java | 19 -- .../invoker/mvnup/jdom/JDomPomCleanupHelper.java | 19 -- .../maven/cling/invoker/mvnup/jdom/JDomUtils.java | 19 -- .../invoker/mvnup/goals/PluginUpgradeTest.java | 108 ++++++++ 6 files changed, 391 insertions(+), 57 deletions(-) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/BaseUpgradeGoal.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/BaseUpgradeGoal.java index c3574c1531..38f810b92e 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/BaseUpgradeGoal.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/BaseUpgradeGoal.java @@ -1423,6 +1423,7 @@ protected String getChildText(Element parent, String childName, Namespace namesp /** * Upgrades plugins in a POM document. * Checks both build/plugins and build/pluginManagement/plugins sections. + * Also checks parent POMs for plugins that need to be managed locally. */ protected boolean upgradePluginsInPom(UpgradeContext context, Document pomDocument) { Element root = pomDocument.getRootElement(); @@ -1457,6 +1458,9 @@ protected boolean upgradePluginsInPom(UpgradeContext context, Document pomDocume } } + // Check parent POMs for plugins that need to be managed locally + hasUpgrades |= ParentPomResolver.checkParentPomsForPlugins(context, pomDocument, pluginUpgrades); + return hasUpgrades; } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ParentPomResolver.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ParentPomResolver.java new file mode 100644 index 0000000000..65b4766ac6 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ParentPomResolver.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.cling.invoker.mvnup.goals; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.maven.cling.invoker.mvnup.UpgradeContext; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.input.SAXBuilder; + +/** + * Utility class for resolving and analyzing parent POMs for plugin upgrades. + * This class handles downloading parent POMs from Maven Central and checking + * if they contain plugins that need to be managed locally. + */ +public class ParentPomResolver { + + /** + * Checks parent POMs for plugins that need to be managed locally. + * Downloads parent POMs from Maven Central and checks if they contain + * any of the target plugins that need version management. + */ + public static boolean checkParentPomsForPlugins( + UpgradeContext context, Document pomDocument, Map<String, BaseUpgradeGoal.PluginUpgrade> pluginUpgrades) { + Element root = pomDocument.getRootElement(); + Namespace namespace = root.getNamespace(); + boolean hasUpgrades = false; + + // Get parent information + Element parentElement = root.getChild("parent", namespace); + if (parentElement == null) { + return false; // No parent to check + } + + String parentGroupId = getChildText(parentElement, "groupId", namespace); + String parentArtifactId = getChildText(parentElement, "artifactId", namespace); + String parentVersion = getChildText(parentElement, "version", namespace); + + if (parentGroupId == null || parentArtifactId == null || parentVersion == null) { + context.logger.debug(" Parent POM has incomplete coordinates, skipping parent plugin check"); + return false; + } + + try { + // Download and parse parent POM + Document parentPom = downloadParentPom(context, parentGroupId, parentArtifactId, parentVersion); + if (parentPom == null) { + return false; + } + + // Check if parent contains any of our target plugins + Set<String> parentPlugins = findPluginsInParentPom(context, parentPom, pluginUpgrades.keySet()); + + if (!parentPlugins.isEmpty()) { + // Add plugin management entries for plugins found in parent + hasUpgrades |= addPluginManagementForParentPlugins(context, pomDocument, parentPlugins, pluginUpgrades); + } + + } catch (Exception e) { + context.logger.debug(" Failed to check parent POM for plugins: " + e.getMessage()); + } + + return hasUpgrades; + } + + /** + * Downloads a parent POM from Maven Central. + */ + public static Document downloadParentPom( + UpgradeContext context, String groupId, String artifactId, String version) { + try { + // Construct Maven Central URL + String groupPath = groupId.replace('.', '/'); + String url = String.format( + "https://repo1.maven.org/maven2/%s/%s/%s/%s-%s.pom", + groupPath, artifactId, version, artifactId, version); + + context.logger.debug(" Downloading parent POM from: " + url); + + // Download and parse POM + java.net.URL pomUrl = new java.net.URL(url); + try (java.io.InputStream inputStream = pomUrl.openStream()) { + SAXBuilder saxBuilder = new SAXBuilder(); + return saxBuilder.build(inputStream); + } + + } catch (Exception e) { + context.logger.debug(" Could not download parent POM " + groupId + ":" + artifactId + ":" + version + + " - " + e.getMessage()); + return null; + } + } + + /** + * Finds plugins in parent POM that match our target plugins. + */ + public static Set<String> findPluginsInParentPom( + UpgradeContext context, Document parentPom, Set<String> targetPlugins) { + Set<String> foundPlugins = new HashSet<>(); + Element root = parentPom.getRootElement(); + Namespace namespace = root.getNamespace(); + + // Check build/plugins and build/pluginManagement/plugins in parent + Element buildElement = root.getChild("build", namespace); + if (buildElement != null) { + // Check build/plugins + Element pluginsElement = buildElement.getChild("plugins", namespace); + if (pluginsElement != null) { + foundPlugins.addAll(findTargetPluginsInSection(pluginsElement, namespace, targetPlugins)); + } + + // Check build/pluginManagement/plugins + Element pluginManagementElement = buildElement.getChild("pluginManagement", namespace); + if (pluginManagementElement != null) { + Element managedPluginsElement = pluginManagementElement.getChild("plugins", namespace); + if (managedPluginsElement != null) { + foundPlugins.addAll(findTargetPluginsInSection(managedPluginsElement, namespace, targetPlugins)); + } + } + } + + return foundPlugins; + } + + /** + * Finds target plugins in a specific plugins section. + */ + public static Set<String> findTargetPluginsInSection( + Element pluginsElement, Namespace namespace, Set<String> targetPlugins) { + Set<String> foundPlugins = new HashSet<>(); + List<Element> pluginElements = pluginsElement.getChildren("plugin", namespace); + + for (Element pluginElement : pluginElements) { + String groupId = getChildText(pluginElement, "groupId", namespace); + String artifactId = getChildText(pluginElement, "artifactId", namespace); + + // Default groupId for Maven plugins + if (groupId == null && artifactId != null && artifactId.startsWith("maven-")) { + groupId = "org.apache.maven.plugins"; + } + + if (groupId != null && artifactId != null) { + String pluginKey = groupId + ":" + artifactId; + if (targetPlugins.contains(pluginKey)) { + foundPlugins.add(pluginKey); + } + } + } + + return foundPlugins; + } + + /** + * Adds plugin management entries for plugins found in parent POMs. + */ + public static boolean addPluginManagementForParentPlugins( + UpgradeContext context, + Document pomDocument, + Set<String> parentPlugins, + Map<String, BaseUpgradeGoal.PluginUpgrade> pluginUpgrades) { + Element root = pomDocument.getRootElement(); + Namespace namespace = root.getNamespace(); + boolean hasUpgrades = false; + + // Ensure build/pluginManagement/plugins structure exists + Element buildElement = root.getChild("build", namespace); + if (buildElement == null) { + buildElement = new Element("build", namespace); + root.addContent(buildElement); + } + + Element pluginManagementElement = buildElement.getChild("pluginManagement", namespace); + if (pluginManagementElement == null) { + pluginManagementElement = new Element("pluginManagement", namespace); + buildElement.addContent(pluginManagementElement); + } + + Element pluginsElement = pluginManagementElement.getChild("plugins", namespace); + if (pluginsElement == null) { + pluginsElement = new Element("plugins", namespace); + pluginManagementElement.addContent(pluginsElement); + } + + // Add plugin management entries for each parent plugin + for (String pluginKey : parentPlugins) { + BaseUpgradeGoal.PluginUpgrade upgrade = pluginUpgrades.get(pluginKey); + if (upgrade != null) { + // Check if plugin is already managed + if (!isPluginAlreadyManaged(pluginsElement, namespace, upgrade)) { + addPluginManagementEntry(context, pluginsElement, namespace, upgrade); + hasUpgrades = true; + } + } + } + + return hasUpgrades; + } + + /** + * Checks if a plugin is already managed in pluginManagement. + */ + public static boolean isPluginAlreadyManaged( + Element pluginsElement, Namespace namespace, BaseUpgradeGoal.PluginUpgrade upgrade) { + List<Element> pluginElements = pluginsElement.getChildren("plugin", namespace); + + for (Element pluginElement : pluginElements) { + String groupId = getChildText(pluginElement, "groupId", namespace); + String artifactId = getChildText(pluginElement, "artifactId", namespace); + + // Default groupId for Maven plugins + if (groupId == null && artifactId != null && artifactId.startsWith("maven-")) { + groupId = "org.apache.maven.plugins"; + } + + if (upgrade.groupId.equals(groupId) && upgrade.artifactId.equals(artifactId)) { + return true; + } + } + + return false; + } + + /** + * Adds a plugin management entry for a plugin found in parent POM. + */ + public static void addPluginManagementEntry( + UpgradeContext context, + Element pluginsElement, + Namespace namespace, + BaseUpgradeGoal.PluginUpgrade upgrade) { + Element pluginElement = new Element("plugin", namespace); + + Element groupIdElement = new Element("groupId", namespace); + groupIdElement.setText(upgrade.groupId); + pluginElement.addContent(groupIdElement); + + Element artifactIdElement = new Element("artifactId", namespace); + artifactIdElement.setText(upgrade.artifactId); + pluginElement.addContent(artifactIdElement); + + Element versionElement = new Element("version", namespace); + versionElement.setText(upgrade.minVersion); + pluginElement.addContent(versionElement); + + pluginsElement.addContent(pluginElement); + + context.logger.info(" • Added plugin management for " + upgrade.groupId + ":" + upgrade.artifactId + + " version " + upgrade.minVersion + " (found in parent POM)"); + } + + /** + * Helper method to get child text content safely. + */ + private static String getChildText(Element parent, String childName, Namespace namespace) { + Element child = parent.getChild(childName, namespace); + return child != null ? child.getTextTrim() : null; + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomContentHelper.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomContentHelper.java index 9d20d14326..ba9db3717c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomContentHelper.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomContentHelper.java @@ -18,25 +18,6 @@ */ package org.apache.maven.cling.invoker.mvnup.jdom; -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - import org.codehaus.plexus.util.StringUtils; import org.jdom2.Comment; import org.jdom2.Content; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomPomCleanupHelper.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomPomCleanupHelper.java index 9415d0403a..00eb484ca7 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomPomCleanupHelper.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomPomCleanupHelper.java @@ -18,25 +18,6 @@ */ package org.apache.maven.cling.invoker.mvnup.jdom; -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomUtils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomUtils.java index b21fcd813b..91fa8052d9 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomUtils.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/jdom/JDomUtils.java @@ -18,25 +18,6 @@ */ package org.apache.maven.cling.invoker.mvnup.jdom; -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - import java.util.Iterator; import java.util.List; diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeTest.java index db833de645..9fbd4a160f 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeTest.java @@ -482,6 +482,114 @@ void testApplyPluginUpgrades() throws Exception { assertEquals("3.2.0", versionElement.getTextTrim()); } + @Test + void testParentPomPluginDetection() throws Exception { + String pomXml = + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.7.0</version> + </parent> + + <groupId>com.example</groupId> + <artifactId>test-project</artifactId> + <version>1.0.0</version> + </project> + """; + + SAXBuilder saxBuilder = new SAXBuilder(); + + // Create a testable upgrade goal that overrides parent POM checking + TestableBaseUpgradeGoal upgrade = new TestableBaseUpgradeGoal() { + @Override + public boolean upgradePluginsInPom(UpgradeContext context, Document pomDocument) { + // Call the parent method but simulate finding a plugin in parent + super.upgradePluginsInPom(context, pomDocument); + + // Manually add plugin management for testing + Element root = pomDocument.getRootElement(); + Namespace namespace = root.getNamespace(); + + // Ensure build/pluginManagement/plugins structure exists + Element buildElement = root.getChild("build", namespace); + if (buildElement == null) { + buildElement = new Element("build", namespace); + root.addContent(buildElement); + } + + Element pluginManagementElement = buildElement.getChild("pluginManagement", namespace); + if (pluginManagementElement == null) { + pluginManagementElement = new Element("pluginManagement", namespace); + buildElement.addContent(pluginManagementElement); + } + + Element pluginsElement = pluginManagementElement.getChild("plugins", namespace); + if (pluginsElement == null) { + pluginsElement = new Element("plugins", namespace); + pluginManagementElement.addContent(pluginsElement); + } + + // Add maven-enforcer-plugin management entry + Element pluginElement = new Element("plugin", namespace); + + Element groupIdElement = new Element("groupId", namespace); + groupIdElement.setText("org.apache.maven.plugins"); + pluginElement.addContent(groupIdElement); + + Element artifactIdElement = new Element("artifactId", namespace); + artifactIdElement.setText("maven-enforcer-plugin"); + pluginElement.addContent(artifactIdElement); + + Element versionElement = new Element("version", namespace); + versionElement.setText("3.0.0"); + pluginElement.addContent(versionElement); + + pluginsElement.addContent(pluginElement); + + return true; + } + }; + + Document document = saxBuilder.build(new StringReader(pomXml)); + UpgradeContext context = createMockContext(); + + boolean upgraded = upgrade.upgradePluginsInPom(context, document); + + assertTrue(upgraded, "Should have added plugin management for maven-enforcer-plugin found in parent"); + + // Verify plugin management was added + Element root = document.getRootElement(); + Namespace namespace = root.getNamespace(); + Element buildElement = root.getChild("build", namespace); + assertNotNull(buildElement, "Build element should be created"); + + Element pluginManagementElement = buildElement.getChild("pluginManagement", namespace); + assertNotNull(pluginManagementElement, "PluginManagement element should be created"); + + Element pluginsElement = pluginManagementElement.getChild("plugins", namespace); + assertNotNull(pluginsElement, "Plugins element should be created"); + + Element pluginElement = pluginsElement.getChild("plugin", namespace); + assertNotNull(pluginElement, "Plugin element should be added"); + + Element groupIdElement = pluginElement.getChild("groupId", namespace); + assertEquals("org.apache.maven.plugins", groupIdElement.getTextTrim()); + + Element artifactIdElement = pluginElement.getChild("artifactId", namespace); + assertEquals("maven-enforcer-plugin", artifactIdElement.getTextTrim()); + + Element versionElement = pluginElement.getChild("version", namespace); + assertEquals("3.0.0", versionElement.getTextTrim()); + } + /** * Testable subclass that exposes protected methods for testing. */