2222import javax .inject .Named ;
2323import 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 ;
3025import java .nio .file .Files ;
3126import java .nio .file .Path ;
3227import java .util .Collections ;
28+ import java .util .Objects ;
3329import java .util .concurrent .ConcurrentHashMap ;
3430import java .util .function .Supplier ;
35- import java .util .stream .Stream ;
3631
3732import org .eclipse .aether .RepositorySystemSession ;
3833import org .eclipse .aether .artifact .Artifact ;
3934import org .eclipse .aether .impl .MetadataResolver ;
35+ import org .eclipse .aether .impl .RemoteRepositoryManager ;
36+ import org .eclipse .aether .internal .impl .filter .prefixes .PrefixesSource ;
4037import org .eclipse .aether .internal .impl .filter .ruletree .PrefixTree ;
4138import org .eclipse .aether .metadata .DefaultMetadata ;
4239import 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 ;
0 commit comments