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/08/06 10:32:07 UTC

(maven) 01/01: [MNG-8765] Pre-interpolate plugin configuration before type conversion

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

gnodet pushed a commit to branch fix-maven-4-property-interpolation-ordering-for-ur
in repository https://gitbox.apache.org/repos/asf/maven.git

commit 5fd4f756f99b896eb5ae23d8bab73205d1a3efce
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu Aug 6 12:31:52 2026 +0200

    [MNG-8765] Pre-interpolate plugin configuration before type conversion
    
    Properties set dynamically at runtime (e.g., by Groovy/GMaven scripts
    via project.properties.setProperty()) are not available during model
    interpolation, which runs before the build lifecycle. When such a
    property is used in a URI-typed plugin parameter like
    https://example.com/${dynamic.prop}/path, the unresolved ${...} reaches
    the UriConverter, which fails with URISyntaxException because curly
    braces are illegal URI characters.
    
    Fix by pre-interpolating the PlexusConfiguration tree using the
    expression evaluator in DefaultMavenPluginManager before passing it to
    the ComponentConfigurator. This ensures all resolvable ${...} references
    are replaced before type converters process the values.
    
    Includes integration tests for URI property interpolation with POM
    properties, inherited parent properties, and CLI-passed properties.
    
    Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
 .../plugin/internal/DefaultMavenPluginManager.java |  66 ++++++++++++++
 ...MavenITmng8765UriPropertyInterpolationTest.java | 101 +++++++++++++++++++++
 .../child/pom.xml                                  |  36 ++++++++
 .../cli-property/pom.xml                           |  58 ++++++++++++
 .../mng-8765-uri-property-interpolation/pom.xml    |  70 ++++++++++++++
 5 files changed, 331 insertions(+)

diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java
index 15f3d8df7b..3f8121a597 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java
@@ -772,6 +772,14 @@ private <T> T loadV3Mojo(
             validator.validate(session, mojoDescriptor, mojo.getClass(), pomConfiguration, expressionEvaluator);
         }
 
+        // MNG-8765: Pre-interpolate configuration values using the expression evaluator before
+        // the ComponentConfigurator processes them. This ensures that ${...} property references
+        // are fully resolved before type converters (like UriConverter) attempt to parse the values.
+        // Without this, properties that are not available during model interpolation (e.g., set
+        // dynamically at runtime by scripts) would reach type converters unresolved, causing
+        // failures like URISyntaxException for URI-typed parameters containing ${...}.
+        pomConfiguration = interpolateConfiguration(pomConfiguration, expressionEvaluator);
+
         populateMojoExecutionFields(
                 mojo,
                 mojoExecution.getExecutionId(),
@@ -872,6 +880,64 @@ private void populateMojoExecutionFields(
         }
     }
 
+    /**
+     * Pre-interpolates a PlexusConfiguration tree by resolving all ${...} property references
+     * in configuration values using the given expression evaluator. This ensures that type
+     * converters (such as UriConverter for java.net.URI parameters) receive fully-resolved
+     * strings instead of raw expressions containing characters like curly braces that are
+     * illegal in certain types.
+     *
+     * <p>This is a defensive measure for properties that are not resolved during model
+     * interpolation — for example, properties set dynamically at runtime by scripts
+     * (e.g., Groovy/GMaven plugins that call {@code project.properties.setProperty(...)}).
+     * Model interpolation runs before the build lifecycle, so it cannot see such properties.
+     * The expression evaluator, which runs at mojo execution time, CAN see them.</p>
+     *
+     * @param configuration the plugin configuration to interpolate
+     * @param evaluator the expression evaluator to resolve ${...} references
+     * @return a new PlexusConfiguration with all resolvable expressions replaced
+     */
+    private PlexusConfiguration interpolateConfiguration(
+            PlexusConfiguration configuration, ExpressionEvaluator evaluator) {
+        // Interpolate the value of this node
+        String value = configuration.getValue(null);
+        if (value != null && value.contains("${")) {
+            try {
+                Object evaluated = evaluator.evaluate(value);
+                if (evaluated instanceof String evaluatedStr) {
+                    if (!evaluatedStr.equals(value)) {
+                        configuration.setValue(evaluatedStr);
+                    }
+                }
+            } catch (ExpressionEvaluationException e) {
+                // Leave value as-is if evaluation fails; the ComponentConfigurator
+                // will report any conversion errors downstream
+            }
+        }
+
+        // Interpolate the default-value attribute if present
+        String defaultValue = configuration.getAttribute("default-value", null);
+        if (defaultValue != null && defaultValue.contains("${")) {
+            try {
+                Object evaluated = evaluator.evaluate(defaultValue);
+                if (evaluated instanceof String evaluatedStr) {
+                    if (!evaluatedStr.equals(defaultValue)) {
+                        configuration.setAttribute("default-value", evaluatedStr);
+                    }
+                }
+            } catch (ExpressionEvaluationException e) {
+                // Leave default-value as-is if evaluation fails
+            }
+        }
+
+        // Recurse into children
+        for (PlexusConfiguration child : configuration.getChildren()) {
+            interpolateConfiguration(child, evaluator);
+        }
+
+        return configuration;
+    }
+
     private void validateParameters(
             MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
             throws ComponentConfigurationException, PluginParameterException {
diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8765UriPropertyInterpolationTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8765UriPropertyInterpolationTest.java
new file mode 100644
index 0000000000..1fd7b0651f
--- /dev/null
+++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITmng8765UriPropertyInterpolationTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.it;
+
+import java.nio.file.Path;
+import java.util.Properties;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This is a test set for
+ * <a href="https://issues.apache.org/jira/browse/MNG-8765">MNG-8765</a>.
+ *
+ * <p>Verifies that property interpolation runs before type conversion for
+ * URI-typed plugin parameters. When a URI parameter contains a property
+ * reference like {@code https://example.com/${my.version}/path}, the property
+ * must be resolved before the string is converted to {@link java.net.URI}.
+ * Otherwise, the curly braces cause a {@link java.net.URISyntaxException}.</p>
+ *
+ * <p>The regression was found in CloudStack (gnodet/maven4-testing#34733) where
+ * a URI parameter with {@code ${cs.version}} defined by a Groovy script at
+ * runtime was not interpolated before URI conversion.</p>
+ */
+public class MavenITmng8765UriPropertyInterpolationTest extends AbstractMavenIntegrationTestCase {
+
+    /**
+     * Verify that property interpolation resolves ${...} in URI-typed plugin
+     * parameters before type conversion, including when properties are
+     * inherited from a parent POM.
+     *
+     * @throws Exception in case of failure
+     */
+    @Test
+    public void testitPomProperty() throws Exception {
+        Path testDir = extractResources("mng-8765-uri-property-interpolation");
+
+        Verifier verifier = newVerifier(testDir);
+        verifier.setAutoclean(false);
+        verifier.deleteDirectory("target");
+        verifier.deleteDirectory("child/target");
+        verifier.addCliArgument("validate");
+        verifier.execute();
+        verifier.verifyErrorFreeLog();
+
+        // Check parent module: property defined in same POM
+        Properties parentProps = verifier.loadProperties("target/plugin-config.properties");
+        assertEquals("https://example.com/1.2.3/path", parentProps.getProperty("uriParam"));
+        assertEquals("https://example.com/1.2.3/path", parentProps.getProperty("urlParam"));
+        assertEquals("1.2.3", parentProps.getProperty("stringParam"));
+
+        // Check child module: property inherited from parent POM
+        Properties childProps = verifier.loadProperties("child/target/plugin-config.properties");
+        assertEquals("https://example.com/1.2.3/path", childProps.getProperty("uriParam"));
+        assertEquals("https://example.com/1.2.3/path", childProps.getProperty("urlParam"));
+        assertEquals("1.2.3", childProps.getProperty("stringParam"));
+    }
+
+    /**
+     * Verify that a property passed via -D on the command line is resolved in
+     * URI-typed plugin parameters. This simulates the CloudStack scenario where
+     * a Groovy script sets a property at runtime via
+     * {@code project.properties.setProperty(...)}.
+     *
+     * @throws Exception in case of failure
+     */
+    @Test
+    public void testitCliProperty() throws Exception {
+        Path testDir = extractResources("mng-8765-uri-property-interpolation/cli-property");
+
+        Verifier verifier = newVerifier(testDir);
+        verifier.setAutoclean(false);
+        verifier.deleteDirectory("target");
+        verifier.addCliArgument("-Dcli.version=2.0.0");
+        verifier.addCliArgument("validate");
+        verifier.execute();
+        verifier.verifyErrorFreeLog();
+
+        Properties props = verifier.loadProperties("target/plugin-config.properties");
+        assertEquals("https://example.com/2.0.0/path", props.getProperty("uriParam"));
+        assertEquals("https://example.com/2.0.0/path", props.getProperty("urlParam"));
+        assertEquals("2.0.0", props.getProperty("stringParam"));
+    }
+}
diff --git a/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/child/pom.xml b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/child/pom.xml
new file mode 100644
index 0000000000..8fbf7bc6f9
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/child/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.its.mng8765</groupId>
+    <artifactId>parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>child</artifactId>
+
+  <name>Maven Integration Test :: MNG-8765 :: Child</name>
+  <description>
+    Child module that inherits the test.version property from the parent
+    and uses it in a URI-typed plugin parameter.
+  </description>
+</project>
diff --git a/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/cli-property/pom.xml b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/cli-property/pom.xml
new file mode 100644
index 0000000000..ed60c0a8e3
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/cli-property/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.mng8765</groupId>
+  <artifactId>cli-property-test</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <name>Maven Integration Test :: MNG-8765 :: CLI Property</name>
+  <description>
+    Verify that a property passed via -D on the CLI is resolved in a URI-typed
+    plugin parameter. The property is NOT defined in POM properties, only via
+    -Dcli.version=2.0.0 on the command line, simulating how some plugins
+    (like Groovy scripts) dynamically set properties at runtime.
+  </description>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.its.plugins</groupId>
+        <artifactId>maven-it-plugin-configuration</artifactId>
+        <version>2.1-SNAPSHOT</version>
+        <configuration>
+          <propertiesFile>target/plugin-config.properties</propertiesFile>
+          <uriParam>https://example.com/${cli.version}/path</uriParam>
+          <urlParam>https://example.com/${cli.version}/path</urlParam>
+          <stringParam>${cli.version}</stringParam>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>config</goal>
+            </goals>
+            <phase>validate</phase>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/pom.xml b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/pom.xml
new file mode 100644
index 0000000000..a2e720d57f
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.mng8765</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <name>Maven Integration Test :: MNG-8765</name>
+  <description>
+    Verify that property interpolation runs before type conversion for URI-typed
+    plugin parameters. When a URI parameter contains a property reference like
+    https://example.com/${my.version}/path, the ${my.version} must be resolved
+    before the string is converted to java.net.URI. Otherwise, the curly braces
+    cause a URISyntaxException.
+  </description>
+
+  <properties>
+    <test.version>1.2.3</test.version>
+  </properties>
+
+  <modules>
+    <module>child</module>
+  </modules>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.its.plugins</groupId>
+        <artifactId>maven-it-plugin-configuration</artifactId>
+        <version>2.1-SNAPSHOT</version>
+        <configuration>
+          <propertiesFile>target/plugin-config.properties</propertiesFile>
+          <!-- Property from POM <properties> -->
+          <uriParam>https://example.com/${test.version}/path</uriParam>
+          <urlParam>https://example.com/${test.version}/path</urlParam>
+          <!-- Property from command line -D (not in POM properties) -->
+          <stringParam>${test.version}</stringParam>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>config</goal>
+            </goals>
+            <phase>validate</phase>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>