You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/10/22 16:38:47 UTC

(maven) branch master updated: Fix #13459: support package-private configuration classes in plugins (#13470)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new d560929d3e Fix #13459: support package-private configuration classes in plugins (#13470)
d560929d3e is described below

commit d560929d3ed7f485f05d31b37c0d86434c8916e4
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu Oct 22 18:38:39 2026 +0200

    Fix #13459: support package-private configuration classes in plugins (#13470)
    
    * Fix #13459: support package-private configuration classes in EnhancedConfigurationConverter
    
    The Plexus AbstractConfigurationConverter.instantiateObject() uses
    Class.newInstance() which requires a public no-arg constructor.
    This prevents using package-private classes (or inner classes) as
    @Parameter configuration types in Maven 4 API plugins.
    
    EnhancedCompositeBeanHelper.setFieldValue() already handles non-public
    fields by calling field.setAccessible(true). This commit makes object
    instantiation symmetric: override the call site in
    EnhancedConfigurationConverter.fromConfiguration() with a local
    instantiate() method that uses getDeclaredConstructor() +
    setAccessible(true), consistent with the field injection strategy.
    
    * test: add regression test for #13459 (package-private constructor instantiation)
---
 .../internal/EnhancedConfigurationConverter.java   |  15 ++-
 .../EnhancedConfigurationConverterTest.java        | 105 +++++++++++++++++++++
 2 files changed, 119 insertions(+), 1 deletion(-)

diff --git a/impl/maven-core/src/main/java/org/apache/maven/configuration/internal/EnhancedConfigurationConverter.java b/impl/maven-core/src/main/java/org/apache/maven/configuration/internal/EnhancedConfigurationConverter.java
index 3f49cfcdba..2a581e83a1 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/configuration/internal/EnhancedConfigurationConverter.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/configuration/internal/EnhancedConfigurationConverter.java
@@ -94,7 +94,7 @@ public Object fromConfiguration(
             if (null == value && implType.isInterface() && configuration.getChildCount() == 0) {
                 return null; // nothing to process
             }
-            final Object bean = instantiateObject(implType);
+            final Object bean = instantiate(implType, configuration);
             if (null == value) {
                 processConfiguration(lookup, bean, loader, configuration, evaluator, listener);
             } else {
@@ -111,6 +111,19 @@ public Object fromConfiguration(
         }
     }
 
+    private static Object instantiate(Class<?> type, PlexusConfiguration configuration)
+            throws ComponentConfigurationException {
+        try {
+            var ctor = type.getDeclaredConstructor();
+            if (!ctor.canAccess(null)) {
+                ctor.setAccessible(true);
+            }
+            return ctor.newInstance();
+        } catch (ReflectiveOperationException e) {
+            throw new ComponentConfigurationException(configuration, "Cannot create instance of class " + type, e);
+        }
+    }
+
     private Class<?> resolveClassForImplementationHint(
             final Class<?> type, final PlexusConfiguration configuration, final ClassLoader loader)
             throws ComponentConfigurationException {
diff --git a/impl/maven-core/src/test/java/org/apache/maven/configuration/internal/EnhancedConfigurationConverterTest.java b/impl/maven-core/src/test/java/org/apache/maven/configuration/internal/EnhancedConfigurationConverterTest.java
new file mode 100644
index 0000000000..7cdc1cbf6c
--- /dev/null
+++ b/impl/maven-core/src/test/java/org/apache/maven/configuration/internal/EnhancedConfigurationConverterTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.configuration.internal;
+
+import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
+import org.codehaus.plexus.component.configurator.ConfigurationListener;
+import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
+import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link EnhancedConfigurationConverter}, focusing on instantiation of
+ * classes with non-public constructors (fix for #13459).
+ */
+class EnhancedConfigurationConverterTest {
+
+    private final EnhancedConfigurationConverter converter = new EnhancedConfigurationConverter();
+    private final DefaultConverterLookup lookup = new DefaultConverterLookup();
+    private final ExpressionEvaluator evaluator = mock(ExpressionEvaluator.class);
+    private final ConfigurationListener listener = mock(ConfigurationListener.class);
+
+    @AfterEach
+    void tearDown() {
+        EnhancedConfigurationConverter.clearCaches();
+    }
+
+    /**
+     * Regression test for #13459: a class with a package-private no-arg constructor must be
+     * instantiable via {@code fromConfiguration()}. Before the fix, the inherited
+     * {@code instantiateObject()} used {@code Class.newInstance()} which requires a public
+     * constructor and would throw {@link IllegalAccessException} for package-private classes.
+     */
+    @Test
+    void testFromConfigurationWithPackagePrivateConstructor() throws Exception {
+        XmlPlexusConfiguration config = new XmlPlexusConfiguration("config");
+        config.addChild("value", "hello");
+
+        Object result = converter.fromConfiguration(
+                lookup, config, PackagePrivateBean.class, null, getClass().getClassLoader(), evaluator, listener);
+
+        assertNotNull(result, "fromConfiguration must return a non-null instance");
+        assertInstanceOf(PackagePrivateBean.class, result, "Result must be a PackagePrivateBean");
+    }
+
+    /**
+     * Verify that a class with no no-arg constructor at all still throws
+     * {@link ComponentConfigurationException} (not an unrelated reflection error).
+     */
+    @Test
+    void testFromConfigurationWithNoDefaultConstructorThrows() {
+        XmlPlexusConfiguration config = new XmlPlexusConfiguration("config");
+
+        assertThrows(
+                ComponentConfigurationException.class,
+                () -> converter.fromConfiguration(
+                        lookup,
+                        config,
+                        NoDefaultConstructorBean.class,
+                        null,
+                        getClass().getClassLoader(),
+                        evaluator,
+                        listener));
+    }
+
+    // -------------------------------------------------------------------------
+    // Helper types — same package so the package-private constructor is valid
+    // -------------------------------------------------------------------------
+
+    /** A bean with a package-private no-arg constructor: the exact scenario fixed by #13459. */
+    static class PackagePrivateBean {
+        String value;
+
+        // package-private constructor — inaccessible via Class.newInstance()
+        PackagePrivateBean() {}
+    }
+
+    /** A bean with no no-arg constructor at all. */
+    static class NoDefaultConstructorBean {
+        @SuppressWarnings("unused")
+        NoDefaultConstructorBean(String required) {}
+    }
+}