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

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new 170b04333 Bug: Filter fixes (#1655)
170b04333 is described below

commit 170b043336c9e26262a7e58db5b0f175db9a4f59
Author: Tamas Cservenak <[email protected]>
AuthorDate: Thu Nov 20 16:16:25 2025 +0100

    Bug: Filter fixes (#1655)
    
    Three major issues:
    * (prefix) one was that it tried to fix the Maven issue of remote 
repository uniqueness (globally), and that attempt was in fact wrong, fix is 
elsewhere. The reason it tried to do this is in fact to circumvent locking 
issues #1644.
    * (manager) other issue was filter lifecycle, manager did it wrongly (data 
is _inherited on customized sessions_): the bug caused that one session was 
used to acquire filters and same filter got used for potentially other (maybe 
even reconfigured) session. This overlook forced filters to implement hoops and 
loops (to prevent recursion), but also prevented any third party code to have 
saying in filter operation.
    * (both filters) make sure RemoteRepositories used as keys for prefixes are 
_same_ (normalized as bare)
    
    Changes:
    * fix manager to make sure filters and session are aligned
    * fix/simplify prefix filter recursion prevention
    * introduce public and documented way to inhibit prefix discovery
    
    Note: as this PR now removes  the "hack" (that in fact tried to circumvent 
#1644) we are now back at state we were before 
https://github.com/apache/maven-resolver/pull/1575 (the "by make repositories 
unique" bit). As we see, this change (without locking fix) will cause Maven IT 
failures, as ITs are executed in parallel, and "prefix discovery" initiated 
over same local repository will/may cause locking conflicts and hence timeouts 
(result is sporadically failing ITs due locking timeout [...]
    
    Fixes #1654
    Fixes #1667
---
 .../DefaultRemoteRepositoryFilterManager.java      |   4 +-
 .../GroupIdRemoteRepositoryFilterSource.java       |  40 ++++-
 .../PrefixesRemoteRepositoryFilterSource.java      | 167 +++++++++++++--------
 .../RemoteRepositoryFilterSourceSupport.java       |  25 +++
 .../PrefixesRemoteRepositoryFilterSourceTest.java  |  42 +++++-
 .../RemoteRepositoryFilterSourceTestSupport.java   |  10 +-
 .../aether/util/repository/RepositoryIdHelper.java |   5 +
 src/site/markdown/configuration.md                 |   6 +-
 8 files changed, 220 insertions(+), 79 deletions(-)

diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/DefaultRemoteRepositoryFilterManager.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/DefaultRemoteRepositoryFilterManager.java
index 276153ce4..42f406416 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/DefaultRemoteRepositoryFilterManager.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/DefaultRemoteRepositoryFilterManager.java
@@ -59,7 +59,9 @@ public final class DefaultRemoteRepositoryFilterManager 
implements RemoteReposit
 
     @Override
     public RemoteRepositoryFilter 
getRemoteRepositoryFilter(RepositorySystemSession session) {
-        return (RemoteRepositoryFilter) 
session.getData().computeIfAbsent(INSTANCE_KEY, () -> {
+        // use session specific key to distinguish between "derived" sessions
+        String instanceSpecificKey = INSTANCE_KEY + "." + session.hashCode();
+        return (RemoteRepositoryFilter) 
session.getData().computeIfAbsent(instanceSpecificKey, () -> {
             HashMap<String, RemoteRepositoryFilter> filters = new HashMap<>();
             for (Map.Entry<String, RemoteRepositoryFilterSource> entry : 
sources.entrySet()) {
                 RemoteRepositoryFilter filter = 
entry.getValue().getRemoteRepositoryFilter(session);
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSource.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSource.java
index 92585d35f..844616bcf 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSource.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSource.java
@@ -109,6 +109,21 @@ public final class GroupIdRemoteRepositoryFilterSource 
extends RemoteRepositoryF
 
     public static final boolean DEFAULT_ENABLED = true;
 
+    /**
+     * Configuration to skip the GroupId filter for given request. This 
configuration is evaluated and if {@code true}
+     * the GroupId remote filter will not kick in.
+     *
+     * @since 2.0.14
+     * @configurationSource {@link 
RepositorySystemSession#getConfigProperties()}
+     * @configurationType {@link java.lang.Boolean}
+     * @configurationRepoIdSuffix Yes
+     * @configurationDefaultValue {@link #DEFAULT_SKIPPED}
+     */
+    public static final String CONFIG_PROP_SKIPPED =
+            RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + 
".skipped";
+
+    public static final boolean DEFAULT_SKIPPED = false;
+
     /**
      * The basedir where to store filter files. If path is relative, it is 
resolved from local repository root.
      *
@@ -160,15 +175,22 @@ public final class GroupIdRemoteRepositoryFilterSource 
extends RemoteRepositoryF
 
     @Override
     protected boolean isEnabled(RepositorySystemSession session) {
-        return ConfigUtils.getBoolean(session, DEFAULT_ENABLED, 
CONFIG_PROP_ENABLED);
+        return ConfigUtils.getBoolean(session, DEFAULT_ENABLED, 
CONFIG_PROP_ENABLED)
+                && !ConfigUtils.getBoolean(session, DEFAULT_SKIPPED, 
CONFIG_PROP_SKIPPED);
     }
 
     private boolean isRepositoryFilteringEnabled(RepositorySystemSession 
session, RemoteRepository remoteRepository) {
         if (isEnabled(session)) {
             return ConfigUtils.getBoolean(
-                    session,
-                    ConfigUtils.getBoolean(session, true, CONFIG_PROP_ENABLED 
+ ".*"),
-                    CONFIG_PROP_ENABLED + "." + remoteRepository.getId());
+                            session,
+                            DEFAULT_ENABLED,
+                            CONFIG_PROP_ENABLED + "." + 
remoteRepository.getId(),
+                            CONFIG_PROP_ENABLED + ".*")
+                    && !ConfigUtils.getBoolean(
+                            session,
+                            DEFAULT_SKIPPED,
+                            CONFIG_PROP_SKIPPED + "." + 
remoteRepository.getId(),
+                            CONFIG_PROP_SKIPPED + ".*");
         }
         return false;
     }
@@ -193,10 +215,11 @@ public final class GroupIdRemoteRepositoryFilterSource 
extends RemoteRepositoryF
                     if (isRepositoryFilteringEnabled(session, 
remoteRepository)) {
                         ruleFile(session, remoteRepository); // populate it; 
needed for save
                         String line = "=" + 
artifactResult.getArtifact().getGroupId();
+                        RemoteRepository normalized = 
normalizeRemoteRepository(session, remoteRepository);
                         recordedRules
-                                .computeIfAbsent(remoteRepository, k -> new 
TreeSet<>())
+                                .computeIfAbsent(normalized, k -> new 
TreeSet<>())
                                 .add(line);
-                        rules.compute(remoteRepository, (k, v) -> {
+                        rules.compute(normalized, (k, v) -> {
                                     if (v == null || v == GroupTree.SENTINEL) {
                                         v = new GroupTree("");
                                     }
@@ -210,7 +233,7 @@ public final class GroupIdRemoteRepositoryFilterSource 
extends RemoteRepositoryF
     }
 
     private Path ruleFile(RepositorySystemSession session, RemoteRepository 
remoteRepository) {
-        return ruleFiles.computeIfAbsent(remoteRepository, r -> getBasedir(
+        return ruleFiles.computeIfAbsent(normalizeRemoteRepository(session, 
remoteRepository), r -> getBasedir(
                         session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, 
false)
                 .resolve(GROUP_ID_FILE_PREFIX
                         + 
RepositoryIdHelper.cachedIdToPathSegment(session).apply(remoteRepository)
@@ -218,7 +241,8 @@ public final class GroupIdRemoteRepositoryFilterSource 
extends RemoteRepositoryF
     }
 
     private GroupTree cacheRules(RepositorySystemSession session, 
RemoteRepository remoteRepository) {
-        return rules.computeIfAbsent(remoteRepository, r -> 
loadRepositoryRules(session, r));
+        return rules.computeIfAbsent(
+                normalizeRemoteRepository(session, remoteRepository), r -> 
loadRepositoryRules(session, r));
     }
 
     private GroupTree loadRepositoryRules(RepositorySystemSession session, 
RemoteRepository remoteRepository) {
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java
index 34698d310..806cfef96 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java
@@ -25,7 +25,6 @@ import javax.inject.Singleton;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
-import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
 
@@ -106,9 +105,7 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
      * <strong>Initial setup:</strong> Don't provide any files - rely on 
auto-discovery as repositories are accessed.
      * <strong>Override when needed:</strong> Create {@code 
prefixes-myrepoId.txt} files in {@code .mvn/rrf/} and
      * commit to version control.
-     * <strong>Caching:</strong> Auto-discovered prefix files are cached in 
the local repository with unique IDs
-     * (using {@link 
RepositoryIdHelper#remoteRepositoryUniqueId(RemoteRepository)}) to prevent 
conflicts that
-     * could cause build failures.
+     * <strong>Caching:</strong> Auto-discovered prefix files are cached in 
the local repository.
      *
      * @configurationSource {@link 
RepositorySystemSession#getConfigProperties()}
      * @configurationType {@link java.lang.Boolean}
@@ -119,6 +116,58 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
 
     public static final boolean DEFAULT_ENABLED = true;
 
+    /**
+     * Configuration to skip the Prefixes filter for given request. This 
configuration is evaluated and if {@code true}
+     * the prefixes remote filter will not kick in. Main use case is by filter 
itself, to prevent recursion during
+     * discovery of remote prefixes file, but this also allows other 
components to control prefix filter discovery, while
+     * leaving configuration like {@link #CONFIG_PROP_ENABLED} still show the 
"real state".
+     *
+     * @since 2.0.14
+     * @configurationSource {@link 
RepositorySystemSession#getConfigProperties()}
+     * @configurationType {@link java.lang.Boolean}
+     * @configurationRepoIdSuffix Yes
+     * @configurationDefaultValue {@link #DEFAULT_SKIPPED}
+     */
+    public static final String CONFIG_PROP_SKIPPED =
+            RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + 
".skipped";
+
+    public static final boolean DEFAULT_SKIPPED = false;
+
+    /**
+     * Configuration to allow Prefixes filter to auto-discover prefixes from 
mirrored repositories as well. For this to
+     * work <em>Maven should be aware</em> that given remote repository is 
mirror and is usually backed by MRM. Given
+     * multiple MRM implementations messes up prefixes file, is better to just 
skip these. In other case, one may use
+     * {@link #CONFIG_PROP_ENABLED} with repository ID suffix.
+     *
+     * @since 2.0.14
+     * @configurationSource {@link 
RepositorySystemSession#getConfigProperties()}
+     * @configurationType {@link java.lang.Boolean}
+     * @configurationRepoIdSuffix Yes
+     * @configurationDefaultValue {@link #DEFAULT_USE_MIRRORED_REPOSITORIES}
+     */
+    public static final String CONFIG_PROP_USE_MIRRORED_REPOSITORIES =
+            RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + 
".useMirroredRepositories";
+
+    public static final boolean DEFAULT_USE_MIRRORED_REPOSITORIES = false;
+
+    /**
+     * Configuration to allow Prefixes filter to auto-discover prefixes from 
repository managers as well. For this to
+     * work <em>Maven should be aware</em> that given remote repository is 
backed by repository manager.
+     * Given multiple MRM implementations messes up prefixes file, is better 
to just skip these. In other case, one may use
+     * {@link #CONFIG_PROP_ENABLED} with repository ID suffix.
+     * <em>Note: as of today, nothing sets this on remote repositories, but is 
added for future.</em>
+     *
+     * @since 2.0.14
+     * @configurationSource {@link 
RepositorySystemSession#getConfigProperties()}
+     * @configurationType {@link java.lang.Boolean}
+     * @configurationRepoIdSuffix Yes
+     * @configurationDefaultValue {@link #DEFAULT_USE_REPOSITORY_MANAGERS}
+     */
+    public static final String CONFIG_PROP_USE_REPOSITORY_MANAGERS =
+            RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + 
".useRepositoryManagers";
+
+    public static final boolean DEFAULT_USE_REPOSITORY_MANAGERS = false;
+
     /**
      * The basedir where to store filter files. If path is relative, it is 
resolved from local repository root.
      *
@@ -146,8 +195,6 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
 
     private final ConcurrentHashMap<RemoteRepository, RepositoryLayout> 
layouts;
 
-    private final ConcurrentHashMap<RemoteRepository, Boolean> ongoingUpdates;
-
     @Inject
     public PrefixesRemoteRepositoryFilterSource(
             Supplier<MetadataResolver> metadataResolver,
@@ -158,20 +205,26 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
         this.repositoryLayoutProvider = 
requireNonNull(repositoryLayoutProvider);
         this.prefixes = new ConcurrentHashMap<>();
         this.layouts = new ConcurrentHashMap<>();
-        this.ongoingUpdates = new ConcurrentHashMap<>();
     }
 
     @Override
     protected boolean isEnabled(RepositorySystemSession session) {
-        return ConfigUtils.getBoolean(session, DEFAULT_ENABLED, 
CONFIG_PROP_ENABLED);
+        return ConfigUtils.getBoolean(session, DEFAULT_ENABLED, 
CONFIG_PROP_ENABLED)
+                && !ConfigUtils.getBoolean(session, DEFAULT_SKIPPED, 
CONFIG_PROP_SKIPPED);
     }
 
     private boolean isRepositoryFilteringEnabled(RepositorySystemSession 
session, RemoteRepository remoteRepository) {
         if (isEnabled(session)) {
             return ConfigUtils.getBoolean(
-                    session,
-                    ConfigUtils.getBoolean(session, true, CONFIG_PROP_ENABLED 
+ ".*"),
-                    CONFIG_PROP_ENABLED + "." + remoteRepository.getId());
+                            session,
+                            DEFAULT_ENABLED,
+                            CONFIG_PROP_ENABLED + "." + 
remoteRepository.getId(),
+                            CONFIG_PROP_ENABLED + ".*")
+                    && !ConfigUtils.getBoolean(
+                            session,
+                            DEFAULT_SKIPPED,
+                            CONFIG_PROP_SKIPPED + "." + 
remoteRepository.getId(),
+                            CONFIG_PROP_SKIPPED + ".*");
         }
         return false;
     }
@@ -190,7 +243,7 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
      * @return the layout instance of {@code null} if layout not supported.
      */
     private RepositoryLayout cacheLayout(RepositorySystemSession session, 
RemoteRepository remoteRepository) {
-        return layouts.computeIfAbsent(remoteRepository, r -> {
+        return layouts.computeIfAbsent(normalizeRemoteRepository(session, 
remoteRepository), r -> {
             try {
                 return repositoryLayoutProvider.newRepositoryLayout(session, 
remoteRepository);
             } catch (NoRepositoryLayoutException e) {
@@ -201,22 +254,9 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
 
     private PrefixTree cachePrefixTree(
             RepositorySystemSession session, Path basedir, RemoteRepository 
remoteRepository) {
-        return ongoingUpdatesGuard(
-                remoteRepository,
-                () -> prefixes.computeIfAbsent(
-                        remoteRepository, r -> loadPrefixTree(session, 
basedir, remoteRepository)),
-                () -> PrefixTree.SENTINEL);
-    }
-
-    private <T> T ongoingUpdatesGuard(RemoteRepository remoteRepository, 
Supplier<T> unblocked, Supplier<T> blocked) {
-        if (!remoteRepository.isBlocked() && null == 
ongoingUpdates.putIfAbsent(remoteRepository, Boolean.TRUE)) {
-            try {
-                return unblocked.get();
-            } finally {
-                ongoingUpdates.remove(remoteRepository);
-            }
-        }
-        return blocked.get();
+        return prefixes.computeIfAbsent(
+                normalizeRemoteRepository(session, remoteRepository),
+                r -> loadPrefixTree(session, basedir, remoteRepository));
     }
 
     private PrefixTree loadPrefixTree(
@@ -225,8 +265,12 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
             String origin = "user-provided";
             Path filePath = resolvePrefixesFromLocalConfiguration(session, 
baseDir, remoteRepository);
             if (filePath == null) {
-                origin = "auto-discovered";
-                filePath = resolvePrefixesFromRemoteRepository(session, 
remoteRepository);
+                if (!supportedResolvePrefixesForRemoteRepository(session, 
remoteRepository)) {
+                    origin = "unsupported";
+                } else {
+                    origin = "auto-discovered";
+                    filePath = resolvePrefixesFromRemoteRepository(session, 
remoteRepository);
+                }
             }
             if (filePath != null) {
                 PrefixesSource prefixesSource = 
PrefixesSource.of(remoteRepository, filePath);
@@ -273,6 +317,18 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
         }
     }
 
+    private boolean supportedResolvePrefixesForRemoteRepository(
+            RepositorySystemSession session, RemoteRepository 
remoteRepository) {
+        if (remoteRepository.isRepositoryManager()) {
+            return ConfigUtils.getBoolean(
+                    session, DEFAULT_USE_REPOSITORY_MANAGERS, 
CONFIG_PROP_USE_REPOSITORY_MANAGERS);
+        } else {
+            return remoteRepository.getMirroredRepositories().isEmpty()
+                    || ConfigUtils.getBoolean(
+                            session, DEFAULT_USE_MIRRORED_REPOSITORIES, 
CONFIG_PROP_USE_MIRRORED_REPOSITORIES);
+        }
+    }
+
     private Path resolvePrefixesFromRemoteRepository(
             RepositorySystemSession session, RemoteRepository 
remoteRepository) {
         MetadataResolver mr = metadataResolver.get();
@@ -282,35 +338,22 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
             RemoteRepository prepared = rm.aggregateRepositories(
                             session, Collections.emptyList(), 
Collections.singletonList(remoteRepository), true)
                     .get(0);
-            // make it unique
-            RemoteRepository unique = new RemoteRepository.Builder(prepared)
-                    
.setId(RepositoryIdHelper.remoteRepositoryUniqueId(remoteRepository))
-                    .build();
-            // supplier for path
-            Supplier<Path> supplier = () -> {
-                MetadataRequest request =
-                        new MetadataRequest(new 
DefaultMetadata(PREFIX_FILE_PATH, Metadata.Nature.RELEASE_OR_SNAPSHOT));
-                // use unique repository; this will result in prefix 
(repository metadata) cached under unique id
-                request.setRepository(unique);
-                request.setDeleteLocalCopyIfMissing(true);
-                request.setFavorLocalRepository(true);
-                MetadataResult result = mr.resolveMetadata(
-                                new 
DefaultRepositorySystemSession(session).setTransferListener(null),
-                                Collections.singleton(request))
-                        .get(0);
-                if (result.isResolved()) {
-                    return result.getMetadata().getPath();
-                } else {
-                    return null;
-                }
-            };
-
-            // prevent recursive calls; but we need extra work if not dealing 
with Central (as in that case outer call
-            // shields us)
-            if (Objects.equals(prepared.getId(), unique.getId())) {
-                return supplier.get();
+            // retrieve prefix as metadata from repository
+            MetadataRequest request =
+                    new MetadataRequest(new DefaultMetadata(PREFIX_FILE_PATH, 
Metadata.Nature.RELEASE_OR_SNAPSHOT));
+            request.setRepository(prepared);
+            request.setDeleteLocalCopyIfMissing(true);
+            request.setFavorLocalRepository(true);
+            MetadataResult result = mr.resolveMetadata(
+                            new DefaultRepositorySystemSession(session)
+                                    .setTransferListener(null)
+                                    .setConfigProperty(CONFIG_PROP_SKIPPED, 
Boolean.TRUE.toString()),
+                            Collections.singleton(request))
+                    .get(0);
+            if (result.isResolved()) {
+                return result.getMetadata().getPath();
             } else {
-                return ongoingUpdatesGuard(unique, supplier, () -> null);
+                return null;
             }
         }
         return null;
@@ -347,15 +390,15 @@ public final class PrefixesRemoteRepositoryFilterSource 
extends RemoteRepository
                     repositoryLayout.getLocation(metadata, false).getPath());
         }
 
-        private Result acceptPrefix(RemoteRepository remoteRepository, String 
path) {
-            PrefixTree prefixTree = cachePrefixTree(session, basedir, 
remoteRepository);
+        private Result acceptPrefix(RemoteRepository repository, String path) {
+            PrefixTree prefixTree = cachePrefixTree(session, basedir, 
repository);
             if (PrefixTree.SENTINEL == prefixTree) {
                 return NOT_PRESENT_RESULT;
             }
             if (prefixTree.acceptedPath(path)) {
-                return new SimpleResult(true, "Path " + path + " allowed from 
" + remoteRepository);
+                return new SimpleResult(true, "Path " + path + " allowed from 
" + repository);
             } else {
-                return new SimpleResult(false, "Prefix " + path + " NOT 
allowed from " + remoteRepository);
+                return new SimpleResult(false, "Path " + path + " NOT allowed 
from " + repository);
             }
         }
     }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
index 5d1744920..bb4c1e3ff 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
@@ -21,9 +21,11 @@ package org.eclipse.aether.internal.impl.filter;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.file.Path;
+import java.util.List;
 
 import org.eclipse.aether.ConfigurationProperties;
 import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilterSource;
 import org.eclipse.aether.util.DirectoryUtils;
@@ -76,6 +78,29 @@ public abstract class RemoteRepositoryFilterSourceSupport 
implements RemoteRepos
         }
     }
 
+    /**
+     * We use remote repositories as keys, but they may fly in as "bare" or as 
"equipped" (w/ auth and proxy) if caller
+     * used {@link 
org.eclipse.aether.RepositorySystem#newResolutionRepositories(RepositorySystemSession,
 List)} beforehand.
+     * The hash/equalTo method factors in all these as well, but from our 
perspective, they do not matter. So we make all
+     * key remote repositories back to "bare".
+     * Ignored properties of normalized repositories:
+     * <ul>
+     *     <li>proxy - is environment dependent</li>
+     *     <li>authentication - is environment and/or user dependent</li>
+     *     <li>mirrored repositories - is environment dependent (within same 
session does not change)</li>
+     *     <li>repository manager - is environment dependent (within same 
session does not change)</li>
+     * </ul>
+     */
+    protected RemoteRepository normalizeRemoteRepository(
+            RepositorySystemSession session, RemoteRepository 
remoteRepository) {
+        return new RemoteRepository.Builder(remoteRepository)
+                .setProxy(null)
+                .setAuthentication(null)
+                .setMirroredRepositories(null)
+                .setRepositoryManager(false)
+                .build();
+    }
+
     /**
      * Simple {@link RemoteRepositoryFilter.Result} immutable implementation.
      */
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java
index 4db77039b..7d7c21782 100644
--- 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java
@@ -25,6 +25,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 
 import org.eclipse.aether.DefaultRepositorySystemSession;
 import org.eclipse.aether.RepositorySystemSession;
@@ -35,11 +36,17 @@ import 
org.eclipse.aether.internal.impl.DefaultArtifactPredicateFactory;
 import org.eclipse.aether.internal.impl.DefaultRepositoryLayoutProvider;
 import org.eclipse.aether.internal.impl.Maven2RepositoryLayoutFactory;
 import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.RepositoryPolicy;
 import org.eclipse.aether.resolution.MetadataRequest;
 import org.eclipse.aether.resolution.MetadataResult;
+import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter;
 import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilterSource;
+import org.junit.jupiter.api.Test;
 
 import static 
org.eclipse.aether.internal.impl.checksum.Checksums.checksumsSelector;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -54,9 +61,24 @@ public class PrefixesRemoteRepositoryFilterSourceTest 
extends RemoteRepositoryFi
         // in test we do not resolve; just reply failed resolution
         MetadataResult failed = new MetadataResult(new MetadataRequest());
         MetadataResolver metadataResolver = mock(MetadataResolver.class);
-        RemoteRepositoryManager remoteRepositoryManager = 
mock(RemoteRepositoryManager.class);
+        RemoteRepositoryManager remoteRepositoryManager = new 
RemoteRepositoryManager() {
+            @Override
+            public List<RemoteRepository> aggregateRepositories(
+                    RepositorySystemSession session,
+                    List<RemoteRepository> dominantRepositories,
+                    List<RemoteRepository> recessiveRepositories,
+                    boolean recessiveIsRaw) {
+                return recessiveRepositories;
+            }
+
+            @Override
+            public RepositoryPolicy getPolicy(
+                    RepositorySystemSession session, RemoteRepository 
repository, boolean releases, boolean snapshots) {
+                throw new UnsupportedOperationException("not implemented");
+            }
+        };
         
when(metadataResolver.resolveMetadata(any(RepositorySystemSession.class), 
any(Collection.class)))
-                .thenReturn(Collections.singletonList(failed));
+                .thenThrow(new IllegalStateException("should not enter here"));
         DefaultRepositoryLayoutProvider layoutProvider = new 
DefaultRepositoryLayoutProvider(Collections.singletonMap(
                 Maven2RepositoryLayoutFactory.NAME,
                 new Maven2RepositoryLayoutFactory(
@@ -91,4 +113,20 @@ public class PrefixesRemoteRepositoryFilterSourceTest 
extends RemoteRepositoryFi
             throw new UncheckedIOException(e);
         }
     }
+
+    @Test
+    void notAcceptedArtifactFromMirror() {
+        RemoteRepository mirror = new RemoteRepository.Builder("mirror", 
"default", "https://irrelevant.com";)
+                .addMirroredRepository(remoteRepository)
+                .build();
+        enableSource(session, true);
+
+        RemoteRepositoryFilter filter = 
subject.getRemoteRepositoryFilter(session);
+        assertNotNull(filter);
+
+        RemoteRepositoryFilter.Result result = filter.acceptArtifact(mirror, 
acceptedArtifact);
+
+        assertTrue(result.isAccepted());
+        assertEquals("Prefix file not present", result.reasoning());
+    }
 }
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceTestSupport.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceTestSupport.java
index 9a149585f..c364aac3b 100644
--- 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceTestSupport.java
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceTestSupport.java
@@ -34,15 +34,15 @@ import static org.junit.jupiter.api.Assertions.*;
  * UT helper for {@link RemoteRepositoryFilterSource} UTs.
  */
 public abstract class RemoteRepositoryFilterSourceTestSupport {
-    private final Artifact acceptedArtifact = new 
DefaultArtifact("org.one:aid:1.0");
+    protected final Artifact acceptedArtifact = new 
DefaultArtifact("org.one:aid:1.0");
 
-    private final Artifact notAcceptedArtifact = new 
DefaultArtifact("org.two:aid:1.0");
+    protected final Artifact notAcceptedArtifact = new 
DefaultArtifact("org.two:aid:1.0");
 
-    private DefaultRepositorySystemSession session;
+    protected DefaultRepositorySystemSession session;
 
-    private RemoteRepository remoteRepository;
+    protected RemoteRepository remoteRepository;
 
-    private RemoteRepositoryFilterSource subject;
+    protected RemoteRepositoryFilterSource subject;
 
     @BeforeEach
     void setup() {
diff --git 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/RepositoryIdHelper.java
 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/RepositoryIdHelper.java
index edcf42e8e..5c8bbbbcd 100644
--- 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/RepositoryIdHelper.java
+++ 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/RepositoryIdHelper.java
@@ -75,6 +75,11 @@ public final class RepositoryIdHelper {
      * different string id. The checksum and update policies are not 
participating in key creation.
      * <p>
      * This method is costly, so should be invoked sparingly, or cache results 
if needed.
+     * <p>
+     * <em>Important:</em>Do not use this method, or at least <em>do consider 
when do you want to use it</em>, as it
+     * totally disconnects repositories used in session. This method may be 
used under some special circumstances
+     * (ie reporting), but <em>must not be used within Resolver (and Maven) 
session for "usual" resolution and
+     * deployment use cases</em>.
      */
     public static String remoteRepositoryUniqueId(RemoteRepository repository) 
{
         if (CENTRAL_DIRECT_ONLY.test(repository)) {
diff --git a/src/site/markdown/configuration.md 
b/src/site/markdown/configuration.md
index e30b9316e..7e1d84040 100644
--- a/src/site/markdown/configuration.md
+++ b/src/site/markdown/configuration.md
@@ -101,8 +101,12 @@ To modify this file, edit the template and regenerate.
 | `"aether.remoteRepositoryFilter.groupId"` | `Boolean` | Configuration to 
enable the GroupId filter (enabled by default). Can be fine-tuned per 
repository using repository ID suffixes. <strong>Important:</strong> For this 
filter to take effect, you must provide configuration files. Without 
configuration files, the enabled filter remains dormant and does not interfere 
with resolution. <strong>Configuration Files:</strong> <ul> <li>Location: 
Directory specified by <code>#CONFIG_PROP_BASED [...]
 | `"aether.remoteRepositoryFilter.groupId.basedir"` | `String` | The basedir 
where to store filter files. If path is relative, it is resolved from local 
repository root. |  `".remoteRepositoryFilters"`  | 1.9.0 |  No  | Session 
Configuration |
 | `"aether.remoteRepositoryFilter.groupId.record"` | `Boolean` | Should filter 
go into "record" mode (and collect encountered artifacts)? |  `false`  | 1.9.0 
|  No  | Session Configuration |
-| `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Configuration to 
enable the Prefixes filter (enabled by default). Can be fine-tuned per 
repository using repository ID suffixes. <strong>Important:</strong> For this 
filter to take effect, configuration files must be available. Without 
configuration files, the enabled filter remains dormant and does not interfere 
with resolution. <strong>Configuration File Resolution:</strong> <ol> 
<li><strong>User-provided files:</strong> Checke [...]
+| `"aether.remoteRepositoryFilter.groupId.skipped"` | `Boolean` | 
Configuration to skip the GroupId filter for given request. This configuration 
is evaluated and if <code>true</code> the GroupId remote filter will not kick 
in. |  `false`  | 2.0.14 |  Yes  | Session Configuration |
+| `"aether.remoteRepositoryFilter.prefixes"` | `Boolean` | Configuration to 
enable the Prefixes filter (enabled by default). Can be fine-tuned per 
repository using repository ID suffixes. <strong>Important:</strong> For this 
filter to take effect, configuration files must be available. Without 
configuration files, the enabled filter remains dormant and does not interfere 
with resolution. <strong>Configuration File Resolution:</strong> <ol> 
<li><strong>User-provided files:</strong> Checke [...]
 | `"aether.remoteRepositoryFilter.prefixes.basedir"` | `String` | The basedir 
where to store filter files. If path is relative, it is resolved from local 
repository root. |  `".remoteRepositoryFilters"`  | 1.9.0 |  No  | Session 
Configuration |
+| `"aether.remoteRepositoryFilter.prefixes.skipped"` | `Boolean` | 
Configuration to skip the Prefixes filter for given request. This configuration 
is evaluated and if <code>true</code> the prefixes remote filter will not kick 
in. Main use case is by filter itself, to prevent recursion during discovery of 
remote prefixes file, but this also allows other components to control prefix 
filter discovery, while leaving configuration like 
<code>#CONFIG_PROP_ENABLED</code> still show the "real st [...]
+| `"aether.remoteRepositoryFilter.prefixes.useMirroredRepositories"` | 
`Boolean` | Configuration to allow Prefixes filter to auto-discover prefixes 
from mirrored repositories as well. For this to work <em>Maven should be 
aware</em> that given remote repository is mirror and is usually backed by MRM. 
Given multiple MRM implementations messes up prefixes file, is better to just 
skip these. In other case, one may use <code>#CONFIG_PROP_ENABLED</code> with 
repository ID suffix. |  `false`  | [...]
+| `"aether.remoteRepositoryFilter.prefixes.useRepositoryManagers"` | `Boolean` 
| Configuration to allow Prefixes filter to auto-discover prefixes from 
repository managers as well. For this to work <em>Maven should be aware</em> 
that given remote repository is backed by repository manager. Given multiple 
MRM implementations messes up prefixes file, is better to just skip these. In 
other case, one may use <code>#CONFIG_PROP_ENABLED</code> with repository ID 
suffix. <em>Note: as of today, n [...]
 | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's 
<code>RepositorySystemSession#getConfigProperties() 
configurationproperties</code> used to store a <code>Boolean</code> flag 
whether this filter should be forced to ban snapshots. By default, snapshots 
are only filtered if the root artifact is not a snapshot. |  `false`  |  |  No  
| Session Configuration |
 | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of 
the directory toi use for locks. If relative path, it is resolved from the 
local repository root. |  `".locks"`  | 1.9.0 |  No  | Session Configuration |
 | `"aether.syncContext.named.discriminating.discriminator"` | `String` | 
Configuration property to pass in discriminator, if needed. If not present, it 
is auto-calculated. |  -  | 1.7.0 |  No  | Session Configuration |


Reply via email to