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 DefaultRemoteRepositoryFilterManager(Map { + // use session specific key to distinguish between "derived" sessions + String instanceSpecificKey = INSTANCE_KEY + "." + session.hashCode(); + return (RemoteRepositoryFilter) session.getData().computeIfAbsent(instanceSpecificKey, () -> { HashMap filters = new HashMap<>(); for (Map.Entry 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 GroupIdRemoteRepositoryFilterSource( @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 void postProcess(RepositorySystemSession session, List ar 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 void postProcess(RepositorySystemSession session, List ar } 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 @@ private Path ruleFile(RepositorySystemSession session, RemoteRepository remoteRe } 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 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 * Initial setup: Don't provide any files - rely on auto-discovery as repositories are accessed. * Override when needed: Create {@code prefixes-myrepoId.txt} files in {@code .mvn/rrf/} and * commit to version control. - * Caching: 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. + * Caching: 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 Maven should be aware 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 Maven should be aware 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. + * Note: as of today, nothing sets this on remote repositories, but is added for future. + * + * @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 layouts; - private final ConcurrentHashMap ongoingUpdates; - @Inject public PrefixesRemoteRepositoryFilterSource( Supplier metadataResolver, @@ -158,20 +205,26 @@ public PrefixesRemoteRepositoryFilterSource( 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 RemoteRepositoryFilter getRemoteRepositoryFilter(RepositorySystemSession * @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 @@ private RepositoryLayout cacheLayout(RepositorySystemSession session, RemoteRepo private PrefixTree cachePrefixTree( RepositorySystemSession session, Path basedir, RemoteRepository remoteRepository) { - return ongoingUpdatesGuard( - remoteRepository, - () -> prefixes.computeIfAbsent( - remoteRepository, r -> loadPrefixTree(session, basedir, remoteRepository)), - () -> PrefixTree.SENTINEL); - } - - private T ongoingUpdatesGuard(RemoteRepository remoteRepository, Supplier unblocked, Supplier 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 @@ private PrefixTree loadPrefixTree( 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 @@ private Path resolvePrefixesFromLocalConfiguration( } } + 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 @@ private Path resolvePrefixesFromRemoteRepository( 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 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 Result acceptMetadata(RemoteRepository remoteRepository, Metadata metadat 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 @@ 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 @@ protected Path getBasedir( } } + /** + * 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: + *
    + *
  • proxy - is environment dependent
  • + *
  • authentication - is environment and/or user dependent
  • + *
  • mirrored repositories - is environment dependent (within same session does not change)
  • + *
  • repository manager - is environment dependent (within same session does not change)
  • + *
+ */ + 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.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.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 @@ protected RemoteRepositoryFilterSource getRemoteRepositoryFilterSource( // 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 aggregateRepositories( + RepositorySystemSession session, + List dominantRepositories, + List 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 @@ protected void allowArtifact( 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 @@ * 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 @@ private RepositoryIdHelper() {} * different string id. The checksum and update policies are not participating in key creation. *

* This method is costly, so should be invoked sparingly, or cache results if needed. + *

+ * Important:Do not use this method, or at least do consider when do you want to use it, as it + * totally disconnects repositories used in session. This method may be used under some special circumstances + * (ie reporting), but must not be used within Resolver (and Maven) session for "usual" resolution and + * deployment use cases. */ 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. Important: 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. Configuration Files:

  • Location: Directory specified by #CONFIG_PROP_BASEDIR (defaults to $LOCAL_REPO/.remoteRepositoryFilters )
  • Naming: groupId-$(repository.id).txt
  • Content: One groupId per line to allow/block from the repository
Recommended Setup (Per-Project): Use project-specific configuration to avoid repository ID clashes. Add to .mvn/maven.config :
 -Daether.remoteRepositoryFilter.groupId=true -Daether.remoteRepositoryFilter.groupId.basedir=${session.rootDirectory}/.mvn/rrf/ 
Then create groupId-myrepoId.txt files in the .mvn/rrf/ directory and commit them to version control. | `true` | 1.9.0 | Yes | Session Configuration | | `"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. Important: 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. Configuration File Resolution:
  1. User-provided files: Checked first from directory specified by #CONFIG_PROP_BASEDIR (defaults to $LOCAL_REPO/.remoteRepositoryFilters )
  2. Auto-discovery: If not found, attempts to download from remote repository and cache locally
File Naming: prefixes-$(repository.id).txt Recommended Setup (Auto-Discovery with Override Capability): Start with auto-discovery, but prepare for project-specific overrides. Add to .mvn/maven.config :
 -Daether.remoteRepositoryFilter.prefixes=true -Daether.remoteRepositoryFilter.prefixes.basedir=${session.rootDirectory}/.mvn/rrf/ 
Initial setup: Don't provide any files - rely on auto-discovery as repositories are accessed. Override when needed: Create prefixes-myrepoId.txt files in .mvn/rrf/ and commit to version control. Caching: Auto-discovered prefix files are cached in the local repository with unique IDs (using RepositoryIdHelper#remoteRepositoryUniqueId(RemoteRepository) ) to prevent conflicts that could cause build failures. | `true` | 1.9.0 | Yes | Session Configuration | +| `"aether.remoteRepositoryFilter.groupId.skipped"` | `Boolean` | Configuration to skip the GroupId filter for given request. This configuration is evaluated and if true 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. Important: 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. Configuration File Resolution:
  1. User-provided files: Checked first from directory specified by #CONFIG_PROP_BASEDIR (defaults to $LOCAL_REPO/.remoteRepositoryFilters )
  2. Auto-discovery: If not found, attempts to download from remote repository and cache locally
File Naming: prefixes-$(repository.id).txt Recommended Setup (Auto-Discovery with Override Capability): Start with auto-discovery, but prepare for project-specific overrides. Add to .mvn/maven.config :
 -Daether.remoteRepositoryFilter.prefixes=true -Daether.remoteRepositoryFilter.prefixes.basedir=${session.rootDirectory}/.mvn/rrf/ 
Initial setup: Don't provide any files - rely on auto-discovery as repositories are accessed. Override when needed: Create prefixes-myrepoId.txt files in .mvn/rrf/ and commit to version control. Caching: Auto-discovered prefix files are cached in the local repository. | `true` | 1.9.0 | Yes | Session Configuration | | `"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 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 #CONFIG_PROP_ENABLED still show the "real state". | `false` | 2.0.14 | Yes | Session Configuration | +| `"aether.remoteRepositoryFilter.prefixes.useMirroredRepositories"` | `Boolean` | Configuration to allow Prefixes filter to auto-discover prefixes from mirrored repositories as well. For this to work Maven should be aware 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 #CONFIG_PROP_ENABLED with repository ID suffix. | `false` | 2.0.14 | Yes | Session Configuration | +| `"aether.remoteRepositoryFilter.prefixes.useRepositoryManagers"` | `Boolean` | Configuration to allow Prefixes filter to auto-discover prefixes from repository managers as well. For this to work Maven should be aware 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 #CONFIG_PROP_ENABLED with repository ID suffix. Note: as of today, nothing sets this on remote repositories, but is added for future. | `false` | 2.0.14 | Yes | Session Configuration | | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's RepositorySystemSession#getConfigProperties() configurationproperties used to store a Boolean 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 |