Skip to content

Commit e5187a9

Browse files
authored
Make RRF enabled by default (#1575)
And introduce few enhancements. Changes: * prefix and groupId filters are enabled by default. * prefix filter is enhanced, to make sure there is no clash of repository IDs (using "truly unique IDs") * prefix file parsing was flakey/incomplete * improve doco * make sure demos use "real central"
1 parent aab9a4c commit e5187a9

File tree

17 files changed

+577
-72
lines changed

17 files changed

+577
-72
lines changed

maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/util/Booter.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.eclipse.aether.RepositorySystemSession;
2929
import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
3030
import org.eclipse.aether.repository.RemoteRepository;
31+
import org.eclipse.aether.repository.RepositoryPolicy;
3132
import org.eclipse.aether.supplier.SessionBuilderSupplier;
3233
import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
3334

@@ -86,6 +87,17 @@ public static List<RemoteRepository> newRepositories(RepositorySystem system, Re
8687
}
8788

8889
private static RemoteRepository newCentralRepository() {
89-
return new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/").build();
90+
return new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/")
91+
.setReleasePolicy(new RepositoryPolicy(
92+
true,
93+
RepositoryPolicy.UPDATE_POLICY_NEVER,
94+
RepositoryPolicy.UPDATE_POLICY_DAILY,
95+
RepositoryPolicy.CHECKSUM_POLICY_FAIL))
96+
.setSnapshotPolicy(new RepositoryPolicy(
97+
false,
98+
RepositoryPolicy.UPDATE_POLICY_NEVER,
99+
RepositoryPolicy.UPDATE_POLICY_DAILY,
100+
RepositoryPolicy.CHECKSUM_POLICY_FAIL))
101+
.build();
90102
}
91103
}

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSource.java

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,26 @@ public final class GroupIdRemoteRepositoryFilterSource extends RemoteRepositoryF
8181
RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".";
8282

8383
/**
84-
* Is filter enabled? Filter must be enabled, and can be "fine-tuned" by repository id appended properties.
84+
* Configuration to enable the GroupId filter (enabled by default). Can be fine-tuned per repository using
85+
* repository ID suffixes.
86+
* <p>
87+
* <strong>Important:</strong> For this filter to take effect, you must provide configuration files. Without
88+
* configuration files, the enabled filter remains dormant and does not interfere with resolution.
89+
* <p>
90+
* <strong>Configuration Files:</strong>
91+
* <ul>
92+
* <li>Location: Directory specified by {@link #CONFIG_PROP_BASEDIR} (defaults to {@code $LOCAL_REPO/.remoteRepositoryFilters})</li>
93+
* <li>Naming: {@code groupId-$(repository.id).txt}</li>
94+
* <li>Content: One groupId per line to allow/block from the repository</li>
95+
* </ul>
96+
* <p>
97+
* <strong>Recommended Setup (Per-Project):</strong>
98+
* Use project-specific configuration to avoid repository ID clashes. Add to {@code .mvn/maven.config}:
99+
* <pre>
100+
* -Daether.remoteRepositoryFilter.groupId=true
101+
* -Daether.remoteRepositoryFilter.groupId.basedir=${session.rootDirectory}/.mvn/rrf/
102+
* </pre>
103+
* Then create {@code groupId-myrepoId.txt} files in the {@code .mvn/rrf/} directory and commit them to version control.
85104
*
86105
* @configurationSource {@link RepositorySystemSession#getConfigProperties()}
87106
* @configurationType {@link java.lang.Boolean}
@@ -90,7 +109,7 @@ public final class GroupIdRemoteRepositoryFilterSource extends RemoteRepositoryF
90109
*/
91110
public static final String CONFIG_PROP_ENABLED = RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME;
92111

93-
public static final boolean DEFAULT_ENABLED = false;
112+
public static final boolean DEFAULT_ENABLED = true;
94113

95114
/**
96115
* The basedir where to store filter files. If path is relative, it is resolved from local repository root.
@@ -201,18 +220,20 @@ private GroupTree cacheRules(RepositorySystemSession session, RemoteRepository r
201220
}
202221

203222
private GroupTree loadRepositoryRules(RepositorySystemSession session, RemoteRepository remoteRepository) {
204-
Path filePath = ruleFile(session, remoteRepository);
205-
if (isRepositoryFilteringEnabled(session, remoteRepository) && Files.isReadable(filePath)) {
206-
try (Stream<String> lines = Files.lines(filePath, StandardCharsets.UTF_8)) {
207-
GroupTree groupTree = new GroupTree("");
208-
int rules = groupTree.loadNodes(lines);
209-
logger.info("Loaded {} group rules for remote repository {}", rules, remoteRepository.getId());
210-
if (logger.isDebugEnabled()) {
211-
groupTree.dump("");
223+
if (isRepositoryFilteringEnabled(session, remoteRepository)) {
224+
Path filePath = ruleFile(session, remoteRepository);
225+
if (Files.isReadable(filePath)) {
226+
try (Stream<String> lines = Files.lines(filePath, StandardCharsets.UTF_8)) {
227+
GroupTree groupTree = new GroupTree("");
228+
int rules = groupTree.loadNodes(lines);
229+
logger.info("Loaded {} group rules for remote repository {}", rules, remoteRepository.getId());
230+
if (logger.isDebugEnabled()) {
231+
groupTree.dump("");
232+
}
233+
return groupTree;
234+
} catch (IOException e) {
235+
throw new UncheckedIOException(e);
212236
}
213-
return groupTree;
214-
} catch (IOException e) {
215-
throw new UncheckedIOException(e);
216237
}
217238
}
218239
return GroupTree.SENTINEL;

maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java

Lines changed: 101 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,18 @@
2222
import javax.inject.Named;
2323
import javax.inject.Singleton;
2424

25-
import java.io.BufferedReader;
26-
import java.io.FileNotFoundException;
27-
import java.io.IOException;
28-
import java.io.UncheckedIOException;
29-
import java.nio.charset.StandardCharsets;
3025
import java.nio.file.Files;
3126
import java.nio.file.Path;
3227
import java.util.Collections;
28+
import java.util.Objects;
3329
import java.util.concurrent.ConcurrentHashMap;
3430
import java.util.function.Supplier;
35-
import java.util.stream.Stream;
3631

3732
import org.eclipse.aether.RepositorySystemSession;
3833
import org.eclipse.aether.artifact.Artifact;
3934
import org.eclipse.aether.impl.MetadataResolver;
35+
import org.eclipse.aether.impl.RemoteRepositoryManager;
36+
import org.eclipse.aether.internal.impl.filter.prefixes.PrefixesSource;
4037
import org.eclipse.aether.internal.impl.filter.ruletree.PrefixTree;
4138
import org.eclipse.aether.metadata.DefaultMetadata;
4239
import org.eclipse.aether.metadata.Metadata;
@@ -88,12 +85,34 @@ public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepository
8885
private static final String PREFIX_FILE_PATH = ".meta/prefixes.txt";
8986

9087
/**
91-
* Visible for UT.
92-
*/
93-
static final String PREFIX_FIRST_LINE = "## repository-prefixes/2.0";
94-
95-
/**
96-
* Is filter enabled? Filter must be enabled, and can be "fine-tuned" by repository id appended properties.
88+
* Configuration to enable the Prefixes filter (enabled by default). Can be fine-tuned per repository using
89+
* repository ID suffixes.
90+
* <p>
91+
* <strong>Important:</strong> For this filter to take effect, configuration files must be available. Without
92+
* configuration files, the enabled filter remains dormant and does not interfere with resolution.
93+
* <p>
94+
* <strong>Configuration File Resolution:</strong>
95+
* <ol>
96+
* <li><strong>User-provided files:</strong> Checked first from directory specified by {@link #CONFIG_PROP_BASEDIR}
97+
* (defaults to {@code $LOCAL_REPO/.remoteRepositoryFilters})</li>
98+
* <li><strong>Auto-discovery:</strong> If not found, attempts to download from remote repository and cache locally</li>
99+
* </ol>
100+
* <p>
101+
* <strong>File Naming:</strong> {@code prefixes-$(repository.id).txt}
102+
* <p>
103+
* <strong>Recommended Setup (Auto-Discovery with Override Capability):</strong>
104+
* Start with auto-discovery, but prepare for project-specific overrides. Add to {@code .mvn/maven.config}:
105+
* <pre>
106+
* -Daether.remoteRepositoryFilter.prefixes=true
107+
* -Daether.remoteRepositoryFilter.prefixes.basedir=${session.rootDirectory}/.mvn/rrf/
108+
* </pre>
109+
* <strong>Initial setup:</strong> Don't provide any files - rely on auto-discovery as repositories are accessed.
110+
* <strong>Override when needed:</strong> Create {@code prefixes-myrepoId.txt} files in {@code .mvn/rrf/} and
111+
* commit to version control.
112+
* <p>
113+
* <strong>Caching:</strong> Auto-discovered prefix files are cached in the local repository with unique IDs
114+
* (using {@link RepositoryIdHelper#remoteRepositoryUniqueId(RemoteRepository)}) to prevent conflicts that
115+
* could cause build failures.
97116
*
98117
* @configurationSource {@link RepositorySystemSession#getConfigProperties()}
99118
* @configurationType {@link java.lang.Boolean}
@@ -102,7 +121,7 @@ public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepository
102121
*/
103122
public static final String CONFIG_PROP_ENABLED = RemoteRepositoryFilterSourceSupport.CONFIG_PROPS_PREFIX + NAME;
104123

105-
public static final boolean DEFAULT_ENABLED = false;
124+
public static final boolean DEFAULT_ENABLED = true;
106125

107126
/**
108127
* The basedir where to store filter files. If path is relative, it is resolved from local repository root.
@@ -123,6 +142,8 @@ public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepository
123142

124143
private final Supplier<MetadataResolver> metadataResolver;
125144

145+
private final Supplier<RemoteRepositoryManager> remoteRepositoryManager;
146+
126147
private final RepositoryLayoutProvider repositoryLayoutProvider;
127148

128149
private final ConcurrentHashMap<RemoteRepository, PrefixTree> prefixes;
@@ -133,8 +154,11 @@ public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepository
133154

134155
@Inject
135156
public PrefixesRemoteRepositoryFilterSource(
136-
Supplier<MetadataResolver> metadataResolver, RepositoryLayoutProvider repositoryLayoutProvider) {
157+
Supplier<MetadataResolver> metadataResolver,
158+
Supplier<RemoteRepositoryManager> remoteRepositoryManager,
159+
RepositoryLayoutProvider repositoryLayoutProvider) {
137160
this.metadataResolver = requireNonNull(metadataResolver);
161+
this.remoteRepositoryManager = requireNonNull(remoteRepositoryManager);
138162
this.repositoryLayoutProvider = requireNonNull(repositoryLayoutProvider);
139163
this.prefixes = new ConcurrentHashMap<>();
140164
this.layouts = new ConcurrentHashMap<>();
@@ -181,15 +205,22 @@ private RepositoryLayout cacheLayout(RepositorySystemSession session, RemoteRepo
181205

182206
private PrefixTree cachePrefixTree(
183207
RepositorySystemSession session, Path basedir, RemoteRepository remoteRepository) {
208+
return ongoingUpdatesGuard(
209+
remoteRepository,
210+
() -> prefixes.computeIfAbsent(
211+
remoteRepository, r -> loadPrefixTree(session, basedir, remoteRepository)),
212+
() -> PrefixTree.SENTINEL);
213+
}
214+
215+
private <T> T ongoingUpdatesGuard(RemoteRepository remoteRepository, Supplier<T> unblocked, Supplier<T> blocked) {
184216
if (!remoteRepository.isBlocked() && null == ongoingUpdates.putIfAbsent(remoteRepository, Boolean.TRUE)) {
185217
try {
186-
return prefixes.computeIfAbsent(
187-
remoteRepository, r -> loadPrefixTree(session, basedir, remoteRepository));
218+
return unblocked.get();
188219
} finally {
189220
ongoingUpdates.remove(remoteRepository);
190221
}
191222
}
192-
return PrefixTree.SENTINEL;
223+
return blocked.get();
193224
}
194225

195226
private PrefixTree loadPrefixTree(
@@ -199,18 +230,27 @@ private PrefixTree loadPrefixTree(
199230
if (filePath == null) {
200231
filePath = resolvePrefixesFromRemoteRepository(session, remoteRepository);
201232
}
202-
if (isPrefixFile(filePath)) {
203-
logger.debug(
204-
"Loading prefixes for remote repository {} from file '{}'", remoteRepository.getId(), filePath);
205-
try (Stream<String> lines = Files.lines(filePath, StandardCharsets.UTF_8)) {
233+
if (filePath != null) {
234+
PrefixesSource prefixesSource = PrefixesSource.of(remoteRepository, filePath);
235+
if (prefixesSource.valid()) {
236+
logger.debug(
237+
"Loaded prefixes for remote repository {} from file '{}'",
238+
prefixesSource.origin().getId(),
239+
prefixesSource.path());
206240
PrefixTree prefixTree = new PrefixTree("");
207-
int rules = prefixTree.loadNodes(lines);
208-
logger.info("Loaded {} prefixes for remote repository {}", rules, remoteRepository.getId());
241+
int rules = prefixTree.loadNodes(prefixesSource.entries().stream());
242+
logger.info(
243+
"Loaded {} prefixes for remote repository {} ({})",
244+
rules,
245+
prefixesSource.origin().getId(),
246+
prefixesSource.path().getFileName());
209247
return prefixTree;
210-
} catch (FileNotFoundException e) {
211-
// strange: we tested for it above, still, we should not fail
212-
} catch (IOException e) {
213-
throw new UncheckedIOException(e);
248+
} else {
249+
logger.info(
250+
"Rejected prefixes for remote repository {} ({}): {}",
251+
prefixesSource.origin().getId(),
252+
prefixesSource.path().getFileName(),
253+
prefixesSource.message());
214254
}
215255
}
216256
logger.debug("Prefix file for remote repository {} not found at '{}'", remoteRepository, filePath);
@@ -232,32 +272,43 @@ private Path resolvePrefixesFromLocalConfiguration(
232272
}
233273
}
234274

235-
private boolean isPrefixFile(Path path) {
236-
if (path == null || !Files.isRegularFile(path)) {
237-
return false;
238-
}
239-
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
240-
return PREFIX_FIRST_LINE.equals(reader.readLine());
241-
} catch (FileNotFoundException e) {
242-
return false;
243-
} catch (IOException e) {
244-
throw new UncheckedIOException(e);
245-
}
246-
}
247-
248275
private Path resolvePrefixesFromRemoteRepository(
249276
RepositorySystemSession session, RemoteRepository remoteRepository) {
250277
MetadataResolver mr = metadataResolver.get();
251-
if (mr != null) {
252-
MetadataRequest request =
253-
new MetadataRequest(new DefaultMetadata(PREFIX_FILE_PATH, Metadata.Nature.RELEASE_OR_SNAPSHOT));
254-
request.setRepository(remoteRepository);
255-
request.setDeleteLocalCopyIfMissing(true);
256-
request.setFavorLocalRepository(true);
257-
MetadataResult result =
258-
mr.resolveMetadata(session, Collections.singleton(request)).get(0);
259-
if (result.isResolved()) {
260-
return result.getMetadata().getPath();
278+
RemoteRepositoryManager rm = remoteRepositoryManager.get();
279+
if (mr != null && rm != null) {
280+
// create "prepared" (auth, proxy and mirror equipped repo)
281+
RemoteRepository prepared = rm.aggregateRepositories(
282+
session, Collections.emptyList(), Collections.singletonList(remoteRepository), true)
283+
.get(0);
284+
// make it unique
285+
RemoteRepository unique = new RemoteRepository.Builder(prepared)
286+
.setId(RepositoryIdHelper.remoteRepositoryUniqueId(remoteRepository))
287+
.build();
288+
// supplier for path
289+
Supplier<Path> supplier = () -> {
290+
MetadataRequest request =
291+
new MetadataRequest(new DefaultMetadata(PREFIX_FILE_PATH, Metadata.Nature.RELEASE_OR_SNAPSHOT));
292+
// use unique repository; this will result in prefix (repository metadata) cached under unique
293+
// id
294+
request.setRepository(unique);
295+
request.setDeleteLocalCopyIfMissing(true);
296+
request.setFavorLocalRepository(true);
297+
MetadataResult result = mr.resolveMetadata(session, Collections.singleton(request))
298+
.get(0);
299+
if (result.isResolved()) {
300+
return result.getMetadata().getPath();
301+
} else {
302+
return null;
303+
}
304+
};
305+
306+
// prevent recursive calls; but we need extra work if not dealing with Central (as in that case outer call
307+
// shields us)
308+
if (Objects.equals(prepared.getId(), unique.getId())) {
309+
return supplier.get();
310+
} else {
311+
return ongoingUpdatesGuard(unique, supplier, () -> null);
261312
}
262313
}
263314
return null;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.eclipse.aether.internal.impl.filter;
20+
21+
import javax.inject.Inject;
22+
import javax.inject.Named;
23+
import javax.inject.Provider;
24+
import javax.inject.Singleton;
25+
26+
import java.util.function.Supplier;
27+
28+
import org.eclipse.aether.impl.RemoteRepositoryManager;
29+
30+
import static java.util.Objects.requireNonNull;
31+
32+
/**
33+
* A bridge to allow injecting plain suppliers, but while in Guice use this one.
34+
*
35+
* @since 2.0.11
36+
*/
37+
@Singleton
38+
@Named
39+
public final class RemoteRepositoryManagerSupplier implements Supplier<RemoteRepositoryManager> {
40+
private final Provider<RemoteRepositoryManager> remoteRepositoryManagerProvider;
41+
42+
@Inject
43+
public RemoteRepositoryManagerSupplier(Provider<RemoteRepositoryManager> remoteRepositoryManagerProvider) {
44+
this.remoteRepositoryManagerProvider = requireNonNull(remoteRepositoryManagerProvider);
45+
}
46+
47+
@Override
48+
public RemoteRepositoryManager get() {
49+
return remoteRepositoryManagerProvider.get();
50+
}
51+
}

0 commit comments

Comments
 (0)