Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Loading