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 @@ -40,14 +40,11 @@
*/
@Experimental
@Immutable
public interface ArtifactResolverRequest extends Request<Session> {
public interface ArtifactResolverRequest extends RepositoryAwareRequest {

@Nonnull
Collection<? extends ArtifactCoordinates> getCoordinates();

@Nullable
List<RemoteRepository> getRepositories();

@Nonnull
static ArtifactResolverRequestBuilder builder() {
return new ArtifactResolverRequestBuilder();
Expand Down Expand Up @@ -127,7 +124,7 @@ private static class DefaultArtifactResolverRequest extends BaseRequest<Session>
@Nonnull List<RemoteRepository> repositories) {
super(session, trace);
this.coordinates = List.copyOf(requireNonNull(coordinates, "coordinates cannot be null"));
this.repositories = repositories;
this.repositories = validate(repositories);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
*/
@Experimental
@Immutable
public interface DependencyResolverRequest extends Request<Session> {
public interface DependencyResolverRequest extends RepositoryAwareRequest {

enum RequestType {
COLLECT,
Expand Down Expand Up @@ -119,9 +119,6 @@ enum RequestType {
@Nullable
Version getTargetVersion();

@Nullable
List<RemoteRepository> getRepositories();

@Nonnull
static DependencyResolverRequestBuilder builder() {
return new DependencyResolverRequestBuilder();
Expand Down Expand Up @@ -479,7 +476,7 @@ public String toString() {
this.pathScope = requireNonNull(pathScope, "pathScope cannot be null");
this.pathTypeFilter = (pathTypeFilter != null) ? pathTypeFilter : DEFAULT_FILTER;
this.targetVersion = targetVersion;
this.repositories = repositories;
this.repositories = validate(repositories);
if (verbose && requestType != RequestType.COLLECT) {
throw new IllegalArgumentException("verbose cannot only be true when collecting dependencies");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
*/
@Experimental
@Immutable
public interface ModelBuilderRequest extends Request<Session> {
public interface ModelBuilderRequest extends RepositoryAwareRequest {

/**
* The possible request types for building a model.
Expand Down Expand Up @@ -133,9 +133,6 @@ enum RepositoryMerging {
@Nonnull
RepositoryMerging getRepositoryMerging();

@Nullable
List<RemoteRepository> getRepositories();

@Nullable
ModelTransformer getLifecycleBindingsInjector();

Expand Down Expand Up @@ -338,7 +335,7 @@ private static class DefaultModelBuilderRequest extends BaseRequest<Session> imp
systemProperties != null ? Map.copyOf(systemProperties) : session.getSystemProperties();
this.userProperties = userProperties != null ? Map.copyOf(userProperties) : session.getUserProperties();
this.repositoryMerging = repositoryMerging;
this.repositories = repositories != null ? List.copyOf(repositories) : null;
this.repositories = repositories != null ? List.copyOf(validate(repositories)) : null;
this.lifecycleBindingsInjector = lifecycleBindingsInjector;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
*/
@Experimental
@Immutable
public interface ProjectBuilderRequest extends Request<Session> {
public interface ProjectBuilderRequest extends RepositoryAwareRequest {

/**
* Gets the path to the project to build.
Expand Down Expand Up @@ -265,7 +265,7 @@ private static class DefaultProjectBuilderRequest extends BaseRequest<Session>
this.allowStubModel = allowStubModel;
this.recursive = recursive;
this.processPlugins = processPlugins;
this.repositories = repositories;
this.repositories = validate(repositories);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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.api.services;

import java.util.HashSet;
import java.util.List;
import java.util.Objects;

import org.apache.maven.api.RemoteRepository;
import org.apache.maven.api.Session;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Immutable;
import org.apache.maven.api.annotations.Nullable;

/**
* Base interface for service requests that involve remote repository operations.
* This interface provides common functionality for requests that need to specify
* and validate remote repositories for artifact resolution, dependency collection,
* model building, and other Maven operations.
*
* <p>Implementations of this interface can specify a list of remote repositories
* to be used during the operation. If no repositories are specified (null),
* the session's default remote repositories will be used. The repositories
* are validated to ensure they don't contain duplicates or null entries.
*
* <p>Remote repositories are used for:
* <ul>
* <li>Resolving artifacts and their metadata</li>
* <li>Downloading parent POMs and dependency POMs</li>
* <li>Retrieving version information and ranges</li>
* <li>Accessing plugin artifacts and their dependencies</li>
* </ul>
*
* <p>Repository validation ensures data integrity by:
* <ul>
* <li>Preventing duplicate repositories that could cause confusion</li>
* <li>Rejecting null repository entries that would cause failures</li>
* <li>Maintaining consistent repository ordering for reproducible builds</li>
* </ul>
*
* @since 4.0.0
* @see RemoteRepository
* @see Session#getRemoteRepositories()
*/
@Experimental
@Immutable
public interface RepositoryAwareRequest extends Request<Session> {

/**
* Returns the list of remote repositories to be used for this request.
*
* <p>If this method returns {@code null}, the session's default remote repositories
* will be used. If a non-null list is returned, it will be used instead of the
* session's repositories, allowing for request-specific repository configuration.
*
* <p>The returned list should not contain duplicate repositories (based on their
* equality) or null entries, as these will cause validation failures when the
* request is processed.
*
* @return the list of remote repositories to use, or {@code null} to use session defaults
* @see Session#getRemoteRepositories()
*/
@Nullable
List<RemoteRepository> getRepositories();

/**
* Validates a list of remote repositories to ensure data integrity.
*
* <p>This method performs the following validations:
* <ul>
* <li>Allows null input (returns null)</li>
* <li>Ensures no duplicate repositories exist in the list</li>
* <li>Ensures no null repository entries exist in the list</li>
* </ul>
*
* <p>Duplicate detection is based on the {@code RemoteRepository#equals(Object)}
* method, which typically compares repository IDs and URLs.
*
* @param repositories the list of repositories to validate, may be {@code null}
* @return the same list if validation passes, or {@code null} if input was {@code null}
* @throws IllegalArgumentException if the list contains duplicate repositories
* @throws IllegalArgumentException if the list contains null repository entries
*/
default List<RemoteRepository> validate(List<RemoteRepository> repositories) {
if (repositories == null) {
return null;
}
HashSet<RemoteRepository> set = new HashSet<>(repositories);
if (repositories.size() != set.size()) {
throw new IllegalArgumentException(
"Repository list contains duplicate entries. Each repository must be unique based on its ID and URL. "
+ "Found " + repositories.size() + " repositories but only " + set.size()
+ " unique entries.");
}
if (repositories.stream().anyMatch(Objects::isNull)) {
throw new IllegalArgumentException(
"Repository list contains null entries. All repository entries must be non-null RemoteRepository instances.");
}
return repositories;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,11 @@
* @since 4.0.0
*/
@Experimental
public interface VersionRangeResolverRequest extends Request<Session> {
public interface VersionRangeResolverRequest extends RepositoryAwareRequest {

@Nonnull
ArtifactCoordinates getArtifactCoordinates();

@Nullable
List<RemoteRepository> getRepositories();

@Nonnull
static VersionRangeResolverRequest build(
@Nonnull Session session, @Nonnull ArtifactCoordinates artifactCoordinates) {
Expand Down Expand Up @@ -111,7 +108,7 @@ private static class DefaultVersionResolverRequest extends BaseRequest<Session>
@Nullable List<RemoteRepository> repositories) {
super(session, trace);
this.artifactCoordinates = artifactCoordinates;
this.repositories = repositories;
this.repositories = validate(repositories);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,11 @@
* @since 4.0.0
*/
@Experimental
public interface VersionResolverRequest extends Request<Session> {
public interface VersionResolverRequest extends RepositoryAwareRequest {

@Nonnull
ArtifactCoordinates getArtifactCoordinates();

@Nullable
List<RemoteRepository> getRepositories();

@Nonnull
static VersionResolverRequest build(@Nonnull Session session, @Nonnull ArtifactCoordinates artifactCoordinates) {
return builder()
Expand Down Expand Up @@ -113,7 +110,7 @@ private static class DefaultVersionResolverRequest extends BaseRequest<Session>
@Nullable List<RemoteRepository> repositories) {
super(session, trace);
this.artifactCoordinates = artifactCoordinates;
this.repositories = repositories;
this.repositories = validate(repositories);
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public ProjectBuildingResult build(Artifact artifact, ProjectBuildingRequest req
public ProjectBuildingResult build(Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request)
throws ProjectBuildingException {
try (BuildSession bs = new BuildSession(request)) {
return bs.build(false, artifact, allowStubModel);
return bs.build(false, artifact, allowStubModel, request.getRemoteRepositories());
}
}

Expand Down Expand Up @@ -316,6 +316,18 @@ class BuildSession implements AutoCloseable {
private final ModelBuilder.ModelBuilderSession modelBuilderSession;
private final Map<String, MavenProject> projectIndex = new ConcurrentHashMap<>(256);

// Store computed repositories per project to avoid leakage between projects
private final Map<String, List<ArtifactRepository>> projectRepositories = new ConcurrentHashMap<>();

/**
* Get the effective repositories for a project. If project-specific repositories
* have been computed and stored, use those; otherwise fall back to request repositories.
*/
private List<ArtifactRepository> getEffectiveRepositories(String projectId) {
List<ArtifactRepository> stored = projectRepositories.get(projectId);
return stored != null ? stored : request.getRemoteRepositories();
}

BuildSession(ProjectBuildingRequest request) {
this.request = request;
InternalSession session = InternalSession.from(request.getRepositorySession());
Expand Down Expand Up @@ -427,7 +439,8 @@ ProjectBuildingResult build(boolean parent, Path pomFile, ModelSource modelSourc
}
}

ProjectBuildingResult build(boolean parent, Artifact artifact, boolean allowStubModel)
ProjectBuildingResult build(
boolean parent, Artifact artifact, boolean allowStubModel, List<ArtifactRepository> repositories)
throws ProjectBuildingException {
org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact(artifact);
pomArtifact = ArtifactDescriptorUtils.toPomArtifact(pomArtifact);
Expand All @@ -436,9 +449,10 @@ ProjectBuildingResult build(boolean parent, Artifact artifact, boolean allowStub

try {
ArtifactCoordinates coordinates = session.createArtifactCoordinates(session.getArtifact(pomArtifact));
// Use provided repositories if available, otherwise fall back to request repositories
ArtifactResolverRequest req = ArtifactResolverRequest.builder()
.session(session)
.repositories(request.getRemoteRepositories().stream()
.repositories(repositories.stream()
.map(RepositoryUtils::toRepo)
.map(session::getRemoteRepository)
.toList())
Expand Down Expand Up @@ -844,7 +858,30 @@ private void initParent(MavenProject project, ModelBuilderResult result) {
// remote repositories with those found in the pom.xml, along with the existing externally
// defined repositories.
//
request.getRemoteRepositories().addAll(project.getRemoteArtifactRepositories());
// Compute merged repositories for this project and store in session
// instead of mutating the shared request to avoid leakage between projects
List<ArtifactRepository> mergedRepositories;
switch (request.getRepositoryMerging()) {
case POM_DOMINANT -> {
LinkedHashSet<ArtifactRepository> reposes =
new LinkedHashSet<>(project.getRemoteArtifactRepositories());
reposes.addAll(request.getRemoteRepositories());
mergedRepositories = List.copyOf(reposes);
}
case REQUEST_DOMINANT -> {
LinkedHashSet<ArtifactRepository> reposes =
new LinkedHashSet<>(request.getRemoteRepositories());
reposes.addAll(project.getRemoteArtifactRepositories());
mergedRepositories = List.copyOf(reposes);
}
default -> throw new IllegalArgumentException(
"Unsupported repository merging: " + request.getRepositoryMerging());
}

// Store the computed repositories for this project in BuildSession storage
// to avoid mutating the shared request and causing leakage between projects
projectRepositories.put(project.getId(), mergedRepositories);

Path parentPomFile = parentModel.getPomFile();
if (parentPomFile != null) {
project.setParentFile(parentPomFile.toFile());
Expand All @@ -864,7 +901,8 @@ private void initParent(MavenProject project, ModelBuilderResult result) {
} else {
Artifact parentArtifact = project.getParentArtifact();
try {
parent = build(true, parentArtifact, false).getProject();
parent = build(true, parentArtifact, false, getEffectiveRepositories(project.getId()))
.getProject();
} catch (ProjectBuildingException e) {
// MNG-4488 where let invalid parents slide on by
if (logger.isDebugEnabled()) {
Expand Down
Loading