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 15f3d8df7b64..3f8121a59717 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 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. + * + *

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.

+ * + * @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 000000000000..1fd7b0651f41 --- /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 + * MNG-8765. + * + *

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}.

+ * + *

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.

+ */ +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 000000000000..8fbf7bc6f95a --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/child/pom.xml @@ -0,0 +1,36 @@ + + + + 4.0.0 + + + org.apache.maven.its.mng8765 + parent + 1.0-SNAPSHOT + + + child + + Maven Integration Test :: MNG-8765 :: Child + + Child module that inherits the test.version property from the parent + and uses it in a URI-typed plugin parameter. + + 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 000000000000..ed60c0a8e32c --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/cli-property/pom.xml @@ -0,0 +1,58 @@ + + + + 4.0.0 + + org.apache.maven.its.mng8765 + cli-property-test + 1.0-SNAPSHOT + + Maven Integration Test :: MNG-8765 :: CLI Property + + 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. + + + + + + org.apache.maven.its.plugins + maven-it-plugin-configuration + 2.1-SNAPSHOT + + target/plugin-config.properties + https://example.com/${cli.version}/path + https://example.com/${cli.version}/path + ${cli.version} + + + + + config + + validate + + + + + + 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 000000000000..a2e720d57f8b --- /dev/null +++ b/its/core-it-suite/src/test/resources/mng-8765-uri-property-interpolation/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + org.apache.maven.its.mng8765 + parent + 1.0-SNAPSHOT + pom + + Maven Integration Test :: MNG-8765 + + 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. + + + + 1.2.3 + + + + child + + + + + + org.apache.maven.its.plugins + maven-it-plugin-configuration + 2.1-SNAPSHOT + + target/plugin-config.properties + + https://example.com/${test.version}/path + https://example.com/${test.version}/path + + ${test.version} + + + + + config + + validate + + + + + +