Skip to content
Merged
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 @@ -33,7 +33,6 @@
import org.apache.maven.api.di.Singleton;
import org.apache.maven.cling.invoker.mvnup.UpgradeContext;
import org.jdom2.Attribute;
import org.jdom2.Comment;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
Expand Down Expand Up @@ -498,25 +497,12 @@ private boolean fixRepositoryExpressions(Element repositoriesElement, Namespace
Element urlElement = repository.getChild("url", namespace);
if (urlElement != null) {
String url = urlElement.getTextTrim();
if (url.contains("${")
&& !url.contains("${project.basedir}")
&& !url.contains("${project.rootDirectory}")) {
if (url.contains("${")) {
// Allow repository URL interpolation; do not disable.
// Keep a gentle warning to help users notice unresolved placeholders at build time.
String repositoryId = getChildText(repository, "id", namespace);
context.warning("Found unsupported expression in " + elementType + " URL (id: " + repositoryId
context.info("Detected interpolated expression in " + elementType + " URL (id: " + repositoryId
+ "): " + url);
context.warning(
"Maven 4 only supports ${project.basedir} and ${project.rootDirectory} expressions in repository URLs");

// Comment out the problematic repository
Comment comment =
new Comment(" Repository disabled due to unsupported expression in URL: " + url + " ");
Element parent = repository.getParentElement();
parent.addContent(parent.indexOf(repository), comment);
removeElementWithFormatting(repository);

context.detail("Fixed: " + "Commented out " + elementType + " with unsupported URL expression (id: "
+ repositoryId + ")");
fixed = true;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
Expand All @@ -63,12 +64,15 @@
import org.apache.maven.api.model.Activation;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.DeploymentRepository;
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.Exclusion;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.InputSource;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Parent;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.model.Repository;
import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.BuilderProblem.Severity;
import org.apache.maven.api.services.Interpolator;
Expand Down Expand Up @@ -1415,6 +1419,29 @@ Model doReadFileModel() throws ModelBuilderException {
model.getParent().getVersion()))
: null)
.build();
// Interpolate repository URLs
if (model.getProjectDirectory() != null) {
String basedir = model.getProjectDirectory().toString();
String basedirUri = model.getProjectDirectory().toUri().toString();
properties.put("basedir", basedir);
properties.put("project.basedir", basedir);
properties.put("project.basedir.uri", basedirUri);
}
try {
String root = request.getSession().getRootDirectory().toString();
String rootUri =
request.getSession().getRootDirectory().toUri().toString();
properties.put("project.rootDirectory", root);
properties.put("project.rootDirectory.uri", rootUri);
} catch (IllegalStateException e) {
}
UnaryOperator<String> callback = properties::get;
model = model.with()
.repositories(interpolateRepository(model.getRepositories(), callback))
.pluginRepositories(interpolateRepository(model.getPluginRepositories(), callback))
.profiles(map(model.getProfiles(), this::interpolateRepository, callback))
.distributionManagement(interpolateRepository(model.getDistributionManagement(), callback))
.build();
// Override model properties with user properties
Map<String, String> newProps = merge(model.getProperties(), session.getUserProperties());
if (newProps != null) {
Expand Down Expand Up @@ -1445,6 +1472,41 @@ Model doReadFileModel() throws ModelBuilderException {
return model;
}

private DistributionManagement interpolateRepository(
DistributionManagement distributionManagement, UnaryOperator<String> callback) {
return distributionManagement == null
? null
: distributionManagement
.with()
.repository((DeploymentRepository)
interpolateRepository(distributionManagement.getRepository(), callback))
.snapshotRepository((DeploymentRepository)
interpolateRepository(distributionManagement.getSnapshotRepository(), callback))
.build();
}

private Profile interpolateRepository(Profile profile, UnaryOperator<String> callback) {
return profile == null
? null
: profile.with()
.repositories(interpolateRepository(profile.getRepositories(), callback))
.pluginRepositories(interpolateRepository(profile.getPluginRepositories(), callback))
.build();
}

private List<Repository> interpolateRepository(List<Repository> repositories, UnaryOperator<String> callback) {
return map(repositories, this::interpolateRepository, callback);
}

private Repository interpolateRepository(Repository repository, UnaryOperator<String> callback) {
return repository == null
? null
: repository
.with()
.url(interpolator.interpolate(repository.getUrl(), callback))
.build();
}

/**
* Merges a list of model profiles with user-defined properties.
* For each property defined in both the model and user properties, the user property value
Expand Down Expand Up @@ -2250,4 +2312,21 @@ Set<String> getContexts() {
return contexts;
}
}

private static <T, A> List<T> map(List<T> resources, BiFunction<T, A, T> mapper, A argument) {
List<T> newResources = null;
if (resources != null) {
for (int i = 0; i < resources.size(); i++) {
T resource = resources.get(i);
T newResource = mapper.apply(resource, argument);
if (newResource != resource) {
if (newResources == null) {
newResources = new ArrayList<>(resources);
}
newResources.set(i, newResource);
}
}
}
return newResources;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -494,15 +494,6 @@ public void validateFileModel(Session s, Model m, int validationLevel, ModelProb
validationLevel);
}

validateRawRepositories(problems, m.getRepositories(), "repositories.repository.", EMPTY, validationLevel);

validateRawRepositories(
problems,
m.getPluginRepositories(),
"pluginRepositories.pluginRepository.",
EMPTY,
validationLevel);

Build build = m.getBuild();
if (build != null) {
validate20RawPlugins(problems, build.getPlugins(), "build.plugins.plugin.", EMPTY, validationLevel);
Expand Down Expand Up @@ -556,16 +547,6 @@ public void validateFileModel(Session s, Model m, int validationLevel, ModelProb
validationLevel);
}

validateRawRepositories(
problems, profile.getRepositories(), prefix, "repositories.repository.", validationLevel);

validateRawRepositories(
problems,
profile.getPluginRepositories(),
prefix,
"pluginRepositories.pluginRepository.",
validationLevel);

BuildBase buildBase = profile.getBuild();
if (buildBase != null) {
validate20RawPlugins(problems, buildBase.getPlugins(), prefix, "plugins.plugin.", validationLevel);
Expand Down Expand Up @@ -635,6 +616,43 @@ public void validateRawModel(Session s, Model m, int validationLevel, ModelProbl
parent);
}
}

if (validationLevel > VALIDATION_LEVEL_MINIMAL) {
validateRawRepositories(problems, m.getRepositories(), "repositories.repository.", EMPTY, validationLevel);

validateRawRepositories(
problems,
m.getPluginRepositories(),
"pluginRepositories.pluginRepository.",
EMPTY,
validationLevel);

for (Profile profile : m.getProfiles()) {
String prefix = "profiles.profile[" + profile.getId() + "].";

validateRawRepositories(
problems, profile.getRepositories(), prefix, "repositories.repository.", validationLevel);

validateRawRepositories(
problems,
profile.getPluginRepositories(),
prefix,
"pluginRepositories.pluginRepository.",
validationLevel);
}

DistributionManagement distMgmt = m.getDistributionManagement();
if (distMgmt != null) {
validateRawRepository(
problems, distMgmt.getRepository(), "distributionManagement.repository.", "", true);
validateRawRepository(
problems,
distMgmt.getSnapshotRepository(),
"distributionManagement.snapshotRepository.",
"",
true);
}
}
}

private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
Expand Down Expand Up @@ -1444,40 +1462,7 @@ private void validateRawRepositories(
Map<String, Repository> index = new HashMap<>();

for (Repository repository : repositories) {
validateStringNotEmpty(
prefix, prefix2, "id", problems, Severity.ERROR, Version.V20, repository.getId(), null, repository);

if (validateStringNotEmpty(
prefix,
prefix2,
"[" + repository.getId() + "].url",
problems,
Severity.ERROR,
Version.V20,
repository.getUrl(),
null,
repository)) {
// only allow ${basedir} and ${project.basedir}
Matcher m = EXPRESSION_NAME_PATTERN.matcher(repository.getUrl());
while (m.find()) {
String expr = m.group(1);
if (!("basedir".equals(expr)
|| "project.basedir".equals(expr)
|| expr.startsWith("project.basedir.")
|| "project.rootDirectory".equals(expr)
|| expr.startsWith("project.rootDirectory."))) {
addViolation(
problems,
Severity.ERROR,
Version.V40,
prefix + prefix2 + "[" + repository.getId() + "].url",
null,
"contains an unsupported expression (only expressions starting with 'project.basedir' or 'project.rootDirectory' are supported).",
repository);
break;
}
}
}
validateRawRepository(problems, repository, prefix, prefix2, false);

String key = repository.getId();

Expand All @@ -1501,6 +1486,44 @@ private void validateRawRepositories(
}
}

private void validateRawRepository(
ModelProblemCollector problems,
Repository repository,
String prefix,
String prefix2,
boolean allowEmptyUrl) {
if (repository == null) {
return;
}
validateStringNotEmpty(
prefix, prefix2, "id", problems, Severity.ERROR, Version.V20, repository.getId(), null, repository);

if (!allowEmptyUrl
&& validateStringNotEmpty(
prefix,
prefix2,
"[" + repository.getId() + "].url",
problems,
Severity.ERROR,
Version.V20,
repository.getUrl(),
null,
repository)) {
// Check for uninterpolated expressions - these should have been interpolated by now
Matcher matcher = EXPRESSION_NAME_PATTERN.matcher(repository.getUrl());
if (matcher.find()) {
addViolation(
problems,
Severity.ERROR,
Version.V40,
prefix + prefix2 + "[" + repository.getId() + "].url",
null,
"contains an uninterpolated expression.",
repository);
}
}
}

private void validate20EffectiveRepository(
ModelProblemCollector problems, Repository repository, String prefix, int validationLevel) {
if (repository != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ void testEmptyPluginVersion() throws Exception {
@Test
void testMissingRepositoryId() throws Exception {
SimpleProblemCollector result =
validateFile("missing-repository-id-pom.xml", ModelValidator.VALIDATION_LEVEL_STRICT);
validateRaw("missing-repository-id-pom.xml", ModelValidator.VALIDATION_LEVEL_STRICT);

assertViolations(result, 0, 4, 0);

Expand Down Expand Up @@ -855,16 +855,23 @@ void testParentVersionRELEASE() throws Exception {
@Test
void repositoryWithExpression() throws Exception {
SimpleProblemCollector result = validateFile("raw-model/repository-with-expression.xml");
assertViolations(result, 0, 1, 0);
assertEquals(
"'repositories.repository.[repo].url' contains an unsupported expression (only expressions starting with 'project.basedir' or 'project.rootDirectory' are supported).",
result.getErrors().get(0));
// Interpolation in repository URLs is allowed; unresolved placeholders will fail later during resolution
assertViolations(result, 0, 0, 0);
}

@Test
void repositoryWithBasedirExpression() throws Exception {
SimpleProblemCollector result = validateRaw("raw-model/repository-with-basedir-expression.xml");
assertViolations(result, 0, 0, 0);
// This test runs on raw model without interpolation, so all expressions appear uninterpolated
// In the real flow, supported expressions would be interpolated before validation
assertViolations(result, 0, 3, 0);
}

@Test
void repositoryWithUnsupportedExpression() throws Exception {
SimpleProblemCollector result = validateRaw("raw-model/repository-with-unsupported-expression.xml");
// Unsupported expressions should cause validation errors
assertViolations(result, 0, 1, 0);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.1.0</modelVersion>

<groupId>org.apache.maven.its.mng0000</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Maven Integration Test :: Test</name>
<description>Test unsupported repository URL expressions that should cause validation errors.</description>

<repositories>
<repository>
<id>repo-unsupported</id>
<url>${project.baseUri}/sdk/maven/repo</url>
</repository>
</repositories>

</project>
Loading
Loading