diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java index 7e8ffc502ae4..3b0dfa65381e 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java @@ -35,6 +35,7 @@ import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Priority; +import org.apache.maven.api.di.Provides; import org.apache.maven.api.di.Singleton; import org.apache.maven.api.model.Build; import org.apache.maven.api.model.Model; @@ -52,10 +53,9 @@ import org.apache.maven.impl.standalone.ApiRunner; import org.codehaus.plexus.components.secdispatcher.Dispatcher; import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher; -import org.eclipse.aether.internal.impl.DefaultPathProcessor; -import org.eclipse.aether.internal.impl.DefaultTransporterProvider; -import org.eclipse.aether.internal.impl.transport.http.DefaultChecksumExtractor; -import org.eclipse.aether.spi.connector.transport.TransporterProvider; +import org.eclipse.aether.spi.connector.transport.TransporterFactory; +import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor; +import org.eclipse.aether.spi.io.PathProcessor; import org.eclipse.aether.transport.file.FileTransporterFactory; import org.eclipse.aether.transport.jdk.JdkTransporterFactory; import org.jdom2.Document; @@ -441,15 +441,7 @@ private Session getSession() { private Session createMaven4Session() { Session session = ApiRunner.createSession(injector -> { injector.bindInstance(Dispatcher.class, new LegacyDispatcher()); - - injector.bindInstance( - TransporterProvider.class, - new DefaultTransporterProvider(Map.of( - "https", - new JdkTransporterFactory( - new DefaultChecksumExtractor(Map.of()), new DefaultPathProcessor()), - "file", - new FileTransporterFactory()))); + injector.bindImplicit(TransporterFactoryConfig.class); }); // Configure repositories @@ -571,7 +563,7 @@ private Map> analyzePluginsUsingEffectiveModels( } } catch (Exception e) { - context.debug("Failed to analyze effective model for " + originalPomPath + ": " + e.getMessage()); + context.warning("Failed to analyze effective model for " + originalPomPath + ": " + e.getMessage()); } } @@ -897,4 +889,24 @@ public static class PluginUpgradeInfo { this.minVersion = minVersion; } } + + /** + * DI configuration that registers transporter factories for the standalone Maven session. + * Uses {@code @Provides} methods so the factories are properly registered as named beans + * and feed into the {@code Map} used by the TransporterProvider. + */ + static class TransporterFactoryConfig { + @Provides + @Named(JdkTransporterFactory.NAME) + static TransporterFactory jdkTransporterFactory( + ChecksumExtractor checksumExtractor, PathProcessor pathProcessor) { + return new JdkTransporterFactory(checksumExtractor, pathProcessor); + } + + @Provides + @Named(FileTransporterFactory.NAME) + static TransporterFactory fileTransporterFactory() { + return new FileTransporterFactory(); + } + } } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java index 69cc3ec0fe91..ada85cdf0e67 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java @@ -43,7 +43,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -522,10 +525,91 @@ void shouldHaveValidPluginUpgradeDefinitions() throws Exception { } } + @Nested + @DisplayName("Inherited Plugin Detection") + class InheritedPluginDetectionTests { + + @Test + @DisplayName("should detect inherited plugins from remote parent POM and add pluginManagement") + void shouldDetectInheritedPluginsFromRemoteParent() throws Exception { + // org.apache:apache:23 defines maven-enforcer-plugin:1.4.1 in pluginManagement. + // A child POM that inherits from this parent should get pluginManagement overrides + // added by mvnup for plugins that need Maven 4 compatibility upgrades. + // Uses an absolute path because the effective model analysis path resolution + // requires it to match between phases. + String pomXml = """ + + + 4.0.0 + + org.apache + apache + 23 + + org.example + test-child + 1.0.0-SNAPSHOT + + """; + + Document document = saxBuilder.build(new StringReader(pomXml)); + Path pomPath = Paths.get("/project/pom.xml").toAbsolutePath(); + Map pomMap = Map.of(pomPath, document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.apply(context, pomMap); + + assertTrue(result.success(), "Strategy should succeed"); + assertTrue(result.modifiedCount() > 0, "Should have added plugin management for inherited plugins"); + + XMLOutputter out = new XMLOutputter(Format.getRawFormat()); + StringWriter writer = new StringWriter(); + out.output(document.getRootElement(), writer); + String xml = writer.toString(); + assertTrue( + xml.contains("maven-enforcer-plugin"), + "Should add pluginManagement for maven-enforcer-plugin inherited from parent"); + } + } + @Nested @DisplayName("Error Handling") class ErrorHandlingTests { + @Test + @DisplayName("should warn when effective model analysis fails for POM with unresolvable remote parent") + void shouldWarnWhenEffectiveModelAnalysisFailsForUnresolvableRemoteParent() throws Exception { + // POM inherits from a remote parent that does not exist. + // The effective model analysis should warn (not silently swallow) the failure. + String pomXml = """ + + + 4.0.0 + + com.nonexistent.test + nonexistent-parent + 1.0.0 + + test-child + + """; + + Document document = saxBuilder.build(new StringReader(pomXml)); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.apply(context, pomMap); + + // Strategy should complete successfully even when effective model analysis fails + assertNotNull(result, "Result should not be null"); + assertTrue(result.success(), "Strategy should succeed even when effective model analysis fails"); + assertTrue(result.processedPoms().contains(Paths.get("pom.xml")), "POM should be marked as processed"); + + // The warning should have been logged (not silently swallowed at debug level) + verify(context.logger, atLeastOnce()) + .warn(argThat(msg -> msg.contains("Failed to analyze effective model"))); + } + @Test @DisplayName("should handle malformed POM gracefully") void shouldHandleMalformedPOMGracefully() throws Exception {