From 978568530bee80aefb03ea75582146e69f5f2379 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 19 Nov 2025 14:57:48 +0100 Subject: [PATCH 01/10] GURK New opt-in feature for enhanced local repository: produce globally unique repository keys. WIP --- .../aether/repository/LocalRepository.java | 3 +- .../repository/WorkspaceRepository.java | 3 +- ...DefaultLocalPathPrefixComposerFactory.java | 5 ++- ...EnhancedLocalRepositoryManagerFactory.java | 45 ++++++++++++++++++- ...LocalPathPrefixComposerFactorySupport.java | 7 +++ .../impl/SimpleLocalRepositoryManager.java | 2 +- .../util/repository/RepositoryIdHelper.java | 38 +++++++++++++++- src/site/markdown/configuration.md | 1 + 8 files changed, 97 insertions(+), 7 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java index 44ae964b0..5e7b24c71 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java @@ -31,6 +31,7 @@ * the repository. */ public final class LocalRepository implements ArtifactRepository { + public static final String ID = "local"; private final Path basePath; @@ -108,7 +109,7 @@ public String getContentType() { @Override public String getId() { - return "local"; + return ID; } /** diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java index e128af209..a922c0b07 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java @@ -27,6 +27,7 @@ * the contained artifacts is handled by a {@link WorkspaceReader}. */ public final class WorkspaceRepository implements ArtifactRepository { + public static final String ID = "workspace"; private final String type; @@ -65,7 +66,7 @@ public String getContentType() { } public String getId() { - return "workspace"; + return ID; } /** diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java index 2a18ce049..509776c10 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java @@ -25,7 +25,8 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.ArtifactRepository; -import org.eclipse.aether.util.repository.RepositoryIdHelper; + +import static org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManagerFactory.repositoryKeyFunction; /** * Default local path prefix composer factory: it fully reuses {@link LocalPathPrefixComposerFactorySupport} class @@ -48,7 +49,7 @@ public LocalPathPrefixComposer createComposer(RepositorySystemSession session) { isSplitRemoteRepositoryLast(session), getReleasesPrefix(session), getSnapshotsPrefix(session), - RepositoryIdHelper.cachedIdToPathSegment(session)); + repositoryKeyFunction(session)); } /** diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java index b00f1dff9..ebc24849a 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java @@ -22,11 +22,15 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.function.Function; + import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.NoLocalRepositoryManagerException; +import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory; import org.eclipse.aether.util.ConfigUtils; import org.eclipse.aether.util.repository.RepositoryIdHelper; @@ -58,6 +62,25 @@ public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryMan public static final String DEFAULT_TRACKING_FILENAME = "_remote.repositories"; + /** + * Make enhanced repository use "globally unique repository keys" (repository keys are used for designating + * cached metadata, artifact availability tracking and split repository prefix production). By default, this + * option is disabled. If enabled, repository keys produced by enhanced repository will be way different + * that those produced with previous versions or without this option enabled. Ideally, you may want to + * use empty local repository to populate with new repository key contained metadata, Interoperability between + * enabled and disabled affects only metadata and split repository (ie. split repository may not find existing + * caches, and may opt to re-download them). + * + * @since 2.0.14 + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link java.lang.Boolean} + * @configurationDefaultValue {@link #DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS} + */ + public static final String CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS = + CONFIG_PROPS_PREFIX + "globallyUniqueRepositoryKeys"; + + public static final boolean DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS = false; + private float priority = 10.0f; private final LocalPathComposer localPathComposer; @@ -66,6 +89,26 @@ public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryMan private final LocalPathPrefixComposerFactory localPathPrefixComposerFactory; + static Function repositoryKeyFunction(RepositorySystemSession session) { + Function idToPathSegmentFunction = + RepositoryIdHelper.cachedIdToPathSegment(session); + Function repositoryKeyFunction = idToPathSegmentFunction; + boolean globallyUniqueRepositoryKeys = ConfigUtils.getBoolean( + session, DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS); + Function globallyUniqueRepositoryKeyFunction = + RepositoryIdHelper.cachedRemoteRepositoryUniqueId(session); + if (globallyUniqueRepositoryKeys) { + repositoryKeyFunction = r -> { + if (r instanceof RemoteRepository) { + return globallyUniqueRepositoryKeyFunction.apply((RemoteRepository) r); + } else { + return idToPathSegmentFunction.apply(r); + } + }; + } + return repositoryKeyFunction; + } + @Inject public EnhancedLocalRepositoryManagerFactory( final LocalPathComposer localPathComposer, @@ -94,7 +137,7 @@ public LocalRepositoryManager newInstance(RepositorySystemSession session, Local return new EnhancedLocalRepositoryManager( repository.getBasePath(), localPathComposer, - RepositoryIdHelper.cachedIdToPathSegment(session), + repositoryKeyFunction(session), trackingFilename, trackingFileManager, localPathPrefixComposerFactory.createComposer(session)); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java index b86223b53..8a70cebd1 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java @@ -220,6 +220,13 @@ protected String getSnapshotsPrefix(RepositorySystemSession session) { session, DEFAULT_SNAPSHOTS_PREFIX, CONFIG_PROP_SNAPSHOTS_PREFIX, R1_CONF_PROP_SNAPSHOTS_PREFIX); } + protected boolean isGloballyUniqueRepositoryKeys(RepositorySystemSession session) { + return ConfigUtils.getBoolean( + session, + EnhancedLocalRepositoryManagerFactory.DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, + EnhancedLocalRepositoryManagerFactory.CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS); + } + /** * Support class for composers: it defines protected members for all the predefined configuration values and * provides default implementation for methods. Implementors may change it's behaviour by overriding methods. diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java index ea751d41a..eee54341e 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java @@ -85,7 +85,7 @@ public String getPathForRemoteArtifact(Artifact artifact, RemoteRepository repos @Override public String getPathForLocalMetadata(Metadata metadata) { requireNonNull(metadata, "metadata cannot be null"); - return localPathComposer.getPathForMetadata(metadata, "local"); + return localPathComposer.getPathForMetadata(metadata, LocalRepository.ID); } @Override 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..ed4c2b482 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 @@ -67,6 +67,39 @@ private RepositoryIdHelper() {} && !remoteRepository.isRepositoryManager() && !remoteRepository.isBlocked(); + /** + * Returns same instance of (session cached) function for session. + * + * @since 2.0.14 + */ + @SuppressWarnings("unchecked") + public static Function cachedRemoteRepositoryUniqueId(RepositorySystemSession session) { + requireNonNull(session, "session"); + return (Function) session.getData() + .computeIfAbsent( + RepositoryIdHelper.class.getSimpleName() + "-remoteRepositoryUniqueIdFunction", + () -> cachedRemoteRepositoryUniqueIdFunction(session)); + } + + /** + * Returns new instance of function backed by cached or uncached (if session has no cache set) + * {@link #remoteRepositoryUniqueId(RemoteRepository)} method call. + */ + @SuppressWarnings("unchecked") + private static Function cachedRemoteRepositoryUniqueIdFunction( + RepositorySystemSession session) { + if (session.getCache() != null) { + return repository -> ((ConcurrentHashMap) session.getCache() + .computeIfAbsent( + session, + RepositoryIdHelper.class.getSimpleName() + "-remoteRepositoryUniqueIdCache", + ConcurrentHashMap::new)) + .computeIfAbsent(repository, id -> remoteRepositoryUniqueId(repository)); + } else { + return RepositoryIdHelper::remoteRepositoryUniqueId; // uncached + } + } + /** * Creates unique repository id for given {@link RemoteRepository}. For Maven Central this method will return * string "central", while for any other remote repository it will return string created as @@ -96,7 +129,10 @@ public static String remoteRepositoryUniqueId(RemoteRepository repository) { buffer.append(", disabled"); } if (repository.isRepositoryManager()) { - buffer.append(", managed("); + buffer.append(", managed"); + } + if (!repository.getMirroredRepositories().isEmpty()) { + buffer.append(", mirrorOf("); for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) { buffer.append(remoteRepositoryUniqueId(mirroredRepo)); } diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index e30b9316e..b510c1fde 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -71,6 +71,7 @@ To modify this file, edit the template and regenerate. | `"aether.generator.sigstore.publicStaging"` | `Boolean` | Whether Sigstore should use public staging sigstage.dev instead of public default sigstore.dev . | `false` | 2.0.2 | No | Session Configuration | | `"aether.interactive"` | `Boolean` | A flag indicating whether interaction with the user is allowed. | `false` | | No | Session Configuration | | `"aether.layout.maven2.checksumAlgorithms"` | `String` | Comma-separated list of checksum algorithms with which checksums are validated (downloaded) and generated (uploaded) with this layout. Resolver by default supports following algorithms: MD5, SHA-1, SHA-256 and SHA-512. New algorithms can be added by implementing ChecksumAlgorithmFactory component. | `"SHA-1,MD5"` | 1.8.0 | Yes | Session Configuration | +| `"aether.lrm.enhanced.globallyUniqueRepositoryKeys"` | `Boolean` | Make enhanced repository use "globally unique repository keys" (repository keys are used for designating cached metadata, artifact availability tracking and split repository prefix production). By default, this option is disabled. If enabled, repository keys produced by enhanced repository will be way different that those produced with previous versions or without this option enabled. Ideally, you may want to use empty local repository to populate with new repository key contained metadata, Interoperability between enabled and disabled affects only metadata and split repository (ie. split repository may not find existing caches, and may opt to re-download them). | `false` | 2.0.14 | No | Session Configuration | | `"aether.lrm.enhanced.localPrefix"` | `String` | The prefix to use for locally installed artifacts. | `"installed"` | 1.8.1 | No | Session Configuration | | `"aether.lrm.enhanced.releasesPrefix"` | `String` | The prefix to use for release artifacts. | `"releases"` | 1.8.1 | No | Session Configuration | | `"aether.lrm.enhanced.remotePrefix"` | `String` | The prefix to use for remotely cached artifacts. | `"cached"` | 1.8.1 | No | Session Configuration | From 6307d5416f06c7717bb64bbb7aeabb6ec5bcb21c Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 19 Nov 2025 15:01:34 +0100 Subject: [PATCH 02/10] Tidy up + javadoc --- .../impl/EnhancedLocalRepositoryManagerFactory.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java index ebc24849a..fb5782195 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java @@ -89,15 +89,21 @@ public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryMan private final LocalPathPrefixComposerFactory localPathPrefixComposerFactory; + /** + * Method that based on configuration returns the "repository key function". Used by {@link EnhancedLocalRepositoryManagerFactory} + * and {@link LocalPathPrefixComposerFactory}. + * + * @since 2.0.14 + */ static Function repositoryKeyFunction(RepositorySystemSession session) { Function idToPathSegmentFunction = RepositoryIdHelper.cachedIdToPathSegment(session); Function repositoryKeyFunction = idToPathSegmentFunction; boolean globallyUniqueRepositoryKeys = ConfigUtils.getBoolean( session, DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS); - Function globallyUniqueRepositoryKeyFunction = - RepositoryIdHelper.cachedRemoteRepositoryUniqueId(session); if (globallyUniqueRepositoryKeys) { + Function globallyUniqueRepositoryKeyFunction = + RepositoryIdHelper.cachedRemoteRepositoryUniqueId(session); repositoryKeyFunction = r -> { if (r instanceof RemoteRepository) { return globallyUniqueRepositoryKeyFunction.apply((RemoteRepository) r); From e5166231d2273c4795bd85c4158941a54cc2c9f3 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 19 Nov 2025 15:40:17 +0100 Subject: [PATCH 03/10] WIP --- .../util/repository/RepositoryIdHelper.java | 77 +++++++------------ 1 file changed, 26 insertions(+), 51 deletions(-) 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 ed4c2b482..16d91594e 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 @@ -46,27 +46,6 @@ public final class RepositoryIdHelper { private RepositoryIdHelper() {} - private static final String CENTRAL_REPOSITORY_ID = "central"; - private static final Collection CENTRAL_URLS = Collections.unmodifiableList(Arrays.asList( - "https://repo.maven.apache.org/maven2", - "https://repo1.maven.org/maven2", - "https://maven-central.storage-download.googleapis.com/maven2")); - private static final Predicate CENTRAL_DIRECT_ONLY = - remoteRepository -> CENTRAL_REPOSITORY_ID.equals(remoteRepository.getId()) - && "https".equals(remoteRepository.getProtocol().toLowerCase(Locale.ENGLISH)) - && CENTRAL_URLS.stream().anyMatch(remoteUrl -> { - String rurl = remoteRepository.getUrl().toLowerCase(Locale.ENGLISH); - if (rurl.endsWith("/")) { - rurl = rurl.substring(0, rurl.length() - 1); - } - return rurl.equals(remoteUrl); - }) - && remoteRepository.getPolicy(false).isEnabled() - && !remoteRepository.getPolicy(true).isEnabled() - && remoteRepository.getMirroredRepositories().isEmpty() - && !remoteRepository.isRepositoryManager() - && !remoteRepository.isBlocked(); - /** * Returns same instance of (session cached) function for session. * @@ -110,40 +89,36 @@ private static Function cachedRemoteRepositoryUniqueId * This method is costly, so should be invoked sparingly, or cache results if needed. */ public static String remoteRepositoryUniqueId(RemoteRepository repository) { - if (CENTRAL_DIRECT_ONLY.test(repository)) { - return CENTRAL_REPOSITORY_ID; + StringBuilder buffer = new StringBuilder(256); + buffer.append(repository.getId()); + buffer.append(" (").append(repository.getUrl()); + buffer.append(", ").append(repository.getContentType()); + boolean r = repository.getPolicy(false).isEnabled(), + s = repository.getPolicy(true).isEnabled(); + if (r && s) { + buffer.append(", releases+snapshots"); + } else if (r) { + buffer.append(", releases"); + } else if (s) { + buffer.append(", snapshots"); } else { - StringBuilder buffer = new StringBuilder(256); - buffer.append(repository.getId()); - buffer.append(" (").append(repository.getUrl()); - buffer.append(", ").append(repository.getContentType()); - boolean r = repository.getPolicy(false).isEnabled(), - s = repository.getPolicy(true).isEnabled(); - if (r && s) { - buffer.append(", releases+snapshots"); - } else if (r) { - buffer.append(", releases"); - } else if (s) { - buffer.append(", snapshots"); - } else { - buffer.append(", disabled"); - } - if (repository.isRepositoryManager()) { - buffer.append(", managed"); - } - if (!repository.getMirroredRepositories().isEmpty()) { - buffer.append(", mirrorOf("); - for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) { - buffer.append(remoteRepositoryUniqueId(mirroredRepo)); - } - buffer.append(")"); - } - if (repository.isBlocked()) { - buffer.append(", blocked"); + buffer.append(", disabled"); + } + if (repository.isRepositoryManager()) { + buffer.append(", managed"); + } + if (!repository.getMirroredRepositories().isEmpty()) { + buffer.append(", mirrorOf("); + for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) { + buffer.append(remoteRepositoryUniqueId(mirroredRepo)); } buffer.append(")"); - return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(buffer.toString()); } + if (repository.isBlocked()) { + buffer.append(", blocked"); + } + buffer.append(")"); + return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(buffer.toString()); } /** From cfe231d64fc3c67c7030d2c61b24dfae69e0a668 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 19 Nov 2025 15:41:14 +0100 Subject: [PATCH 04/10] Remove unsed --- .../impl/LocalPathPrefixComposerFactorySupport.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java index 8a70cebd1..b86223b53 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java @@ -220,13 +220,6 @@ protected String getSnapshotsPrefix(RepositorySystemSession session) { session, DEFAULT_SNAPSHOTS_PREFIX, CONFIG_PROP_SNAPSHOTS_PREFIX, R1_CONF_PROP_SNAPSHOTS_PREFIX); } - protected boolean isGloballyUniqueRepositoryKeys(RepositorySystemSession session) { - return ConfigUtils.getBoolean( - session, - EnhancedLocalRepositoryManagerFactory.DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, - EnhancedLocalRepositoryManagerFactory.CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS); - } - /** * Support class for composers: it defines protected members for all the predefined configuration values and * provides default implementation for methods. Implementors may change it's behaviour by overriding methods. From ab265fa51d36bb2f1fc0b67cc7927eb720acf5e5 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 19 Nov 2025 15:51:02 +0100 Subject: [PATCH 05/10] WIP --- .../eclipse/aether/util/repository/RepositoryIdHelper.java | 5 ----- 1 file changed, 5 deletions(-) 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 16d91594e..982a57fc0 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 @@ -18,13 +18,8 @@ */ package org.eclipse.aether.util.repository; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import java.util.function.Predicate; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.ArtifactRepository; From f11bcf7792195d32815ce14478fe7697075619c7 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 19 Nov 2025 21:04:56 +0100 Subject: [PATCH 06/10] WIP --- ...DefaultLocalPathPrefixComposerFactory.java | 6 +- .../impl/EnhancedLocalRepositoryManager.java | 7 +- ...EnhancedLocalRepositoryManagerFactory.java | 19 +-- ...LocalPathPrefixComposerFactorySupport.java | 15 +- .../impl/SimpleLocalRepositoryManager.java | 37 +---- .../SimpleLocalRepositoryManagerFactory.java | 2 +- .../util/repository/RepositoryIdHelper.java | 141 ++++++++---------- 7 files changed, 92 insertions(+), 135 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java index 509776c10..08defd8d6 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java @@ -21,10 +21,12 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.ArtifactRepository; +import org.eclipse.aether.repository.RemoteRepository; import static org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManagerFactory.repositoryKeyFunction; @@ -67,7 +69,7 @@ private DefaultLocalPathPrefixComposer( boolean splitRemoteRepositoryLast, String releasesPrefix, String snapshotsPrefix, - Function idToPathSegmentFunction) { + BiFunction repositoryKeyFunction) { super( split, localPrefix, @@ -78,7 +80,7 @@ private DefaultLocalPathPrefixComposer( splitRemoteRepositoryLast, releasesPrefix, snapshotsPrefix, - idToPathSegmentFunction); + repositoryKeyFunction); } } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java index e55150f3f..c1b80999f 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManager.java @@ -27,12 +27,11 @@ import java.util.Map; import java.util.Objects; import java.util.Properties; -import java.util.function.Function; +import java.util.function.BiFunction; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.metadata.Metadata; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.LocalArtifactRegistration; import org.eclipse.aether.repository.LocalArtifactRequest; import org.eclipse.aether.repository.LocalArtifactResult; @@ -72,11 +71,11 @@ class EnhancedLocalRepositoryManager extends SimpleLocalRepositoryManager { EnhancedLocalRepositoryManager( Path basedir, LocalPathComposer localPathComposer, - Function idToPathSegmentFunction, + BiFunction repositoryKeyFunction, String trackingFilename, TrackingFileManager trackingFileManager, LocalPathPrefixComposer localPathPrefixComposer) { - super(basedir, "enhanced", localPathComposer, idToPathSegmentFunction); + super(basedir, "enhanced", localPathComposer, repositoryKeyFunction); this.trackingFilename = requireNonNull(trackingFilename); this.trackingFileManager = requireNonNull(trackingFileManager); this.localPathPrefixComposer = requireNonNull(localPathPrefixComposer); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java index fb5782195..c4d030bdd 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java @@ -22,6 +22,7 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.aether.ConfigurationProperties; @@ -95,24 +96,14 @@ public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryMan * * @since 2.0.14 */ - static Function repositoryKeyFunction(RepositorySystemSession session) { - Function idToPathSegmentFunction = - RepositoryIdHelper.cachedIdToPathSegment(session); - Function repositoryKeyFunction = idToPathSegmentFunction; + static BiFunction repositoryKeyFunction(RepositorySystemSession session) { boolean globallyUniqueRepositoryKeys = ConfigUtils.getBoolean( session, DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS); if (globallyUniqueRepositoryKeys) { - Function globallyUniqueRepositoryKeyFunction = - RepositoryIdHelper.cachedRemoteRepositoryUniqueId(session); - repositoryKeyFunction = r -> { - if (r instanceof RemoteRepository) { - return globallyUniqueRepositoryKeyFunction.apply((RemoteRepository) r); - } else { - return idToPathSegmentFunction.apply(r); - } - }; + return RepositoryIdHelper::globallyUniqueRepositoryKey; + } else { + return RepositoryIdHelper::simpleRepositoryKey; } - return repositoryKeyFunction; } @Inject diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java index b86223b53..e22fcc801 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java @@ -18,6 +18,7 @@ */ package org.eclipse.aether.internal.impl; +import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.aether.RepositorySystemSession; @@ -244,7 +245,7 @@ protected abstract static class LocalPathPrefixComposerSupport implements LocalP protected final String snapshotsPrefix; - protected final Function idToPathSegmentFunction; + protected final BiFunction repositoryKeyFunction; protected LocalPathPrefixComposerSupport( boolean split, @@ -256,7 +257,7 @@ protected LocalPathPrefixComposerSupport( boolean splitRemoteRepositoryLast, String releasesPrefix, String snapshotsPrefix, - Function idToPathSegmentFunction) { + BiFunction repositoryKeyFunction) { this.split = split; this.localPrefix = localPrefix; this.splitLocal = splitLocal; @@ -266,7 +267,7 @@ protected LocalPathPrefixComposerSupport( this.splitRemoteRepositoryLast = splitRemoteRepositoryLast; this.releasesPrefix = releasesPrefix; this.snapshotsPrefix = snapshotsPrefix; - this.idToPathSegmentFunction = idToPathSegmentFunction; + this.repositoryKeyFunction = repositoryKeyFunction; } @Override @@ -288,13 +289,13 @@ public String getPathPrefixForRemoteArtifact(Artifact artifact, RemoteRepository } String result = remotePrefix; if (!splitRemoteRepositoryLast && splitRemoteRepository) { - result += "/" + idToPathSegmentFunction.apply(repository); + result += "/" + repositoryKeyFunction.apply(repository, null); } if (splitRemote) { result += "/" + (artifact.isSnapshot() ? snapshotsPrefix : releasesPrefix); } if (splitRemoteRepositoryLast && splitRemoteRepository) { - result += "/" + idToPathSegmentFunction.apply(repository); + result += "/" + repositoryKeyFunction.apply(repository, null); } return result; } @@ -318,13 +319,13 @@ public String getPathPrefixForRemoteMetadata(Metadata metadata, RemoteRepository } String result = remotePrefix; if (!splitRemoteRepositoryLast && splitRemoteRepository) { - result += "/" + idToPathSegmentFunction.apply(repository); + result += "/" + repositoryKeyFunction.apply(repository, null); } if (splitRemote) { result += "/" + (isSnapshot(metadata) ? snapshotsPrefix : releasesPrefix); } if (splitRemoteRepositoryLast && splitRemoteRepository) { - result += "/" + idToPathSegmentFunction.apply(repository); + result += "/" + repositoryKeyFunction.apply(repository, null); } return result; } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java index eee54341e..82c1f0ad7 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; +import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.aether.RepositorySystemSession; @@ -51,17 +52,17 @@ class SimpleLocalRepositoryManager implements LocalRepositoryManager { private final LocalPathComposer localPathComposer; - private final Function idToPathSegmentFunction; + private final BiFunction repositoryKeyFunction; SimpleLocalRepositoryManager( Path basePath, String type, LocalPathComposer localPathComposer, - Function idToPathSegmentFunction) { + BiFunction repositoryKeyFunction) { requireNonNull(basePath, "base directory cannot be null"); repository = new LocalRepository(basePath.toAbsolutePath(), type); this.localPathComposer = requireNonNull(localPathComposer); - this.idToPathSegmentFunction = requireNonNull(idToPathSegmentFunction); + this.repositoryKeyFunction = requireNonNull(repositoryKeyFunction); } @Override @@ -101,35 +102,7 @@ public String getPathForRemoteMetadata(Metadata metadata, RemoteRepository repos * of the remote repository (as it may change). */ protected String getRepositoryKey(RemoteRepository repository, String context) { - String key; - - if (repository.isRepositoryManager()) { - // repository serves dynamic contents, take request parameters into account for key - - StringBuilder buffer = new StringBuilder(128); - - buffer.append(idToPathSegmentFunction.apply(repository)); - - buffer.append('-'); - - SortedSet subKeys = new TreeSet<>(); - for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) { - subKeys.add(mirroredRepo.getId()); - } - - StringDigestUtil sha1 = StringDigestUtil.sha1(); - sha1.update(context); - for (String subKey : subKeys) { - sha1.update(subKey); - } - buffer.append(sha1.digest()); - - key = buffer.toString(); - } else { - key = idToPathSegmentFunction.apply(repository); - } - - return key; + return repositoryKeyFunction.apply(repository, context); } @Override diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java index c1e4ccd21..3e02825e5 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java @@ -65,7 +65,7 @@ public LocalRepositoryManager newInstance(RepositorySystemSession session, Local repository.getBasePath(), "simple", localPathComposer, - RepositoryIdHelper.cachedIdToPathSegment(session)); + RepositoryIdHelper::simpleRepositoryKey); } else { throw new NoLocalRepositoryManagerException(repository); } 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 982a57fc0..a836517b9 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 @@ -18,7 +18,11 @@ */ package org.eclipse.aether.util.repository; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.aether.RepositorySystemSession; @@ -42,36 +46,47 @@ public final class RepositoryIdHelper { private RepositoryIdHelper() {} /** - * Returns same instance of (session cached) function for session. + * Simple {@code repositoryKey} function (classic). Returns {@link RemoteRepository#getId()}, unless + * {@link RemoteRepository#isRepositoryManager()} returns {@code true}, in which case this method creates + * unique identifier based on ID and current configuration of the remote repository (as it may change). * * @since 2.0.14 - */ - @SuppressWarnings("unchecked") - public static Function cachedRemoteRepositoryUniqueId(RepositorySystemSession session) { - requireNonNull(session, "session"); - return (Function) session.getData() - .computeIfAbsent( - RepositoryIdHelper.class.getSimpleName() + "-remoteRepositoryUniqueIdFunction", - () -> cachedRemoteRepositoryUniqueIdFunction(session)); + **/ + public static String simpleRepositoryKey(RemoteRepository repository, String context) { + String key; + if (repository.isRepositoryManager()) { + StringBuilder buffer = new StringBuilder(128); + buffer.append(idToPathSegment(repository)); + buffer.append('-'); + SortedSet subKeys = new TreeSet<>(); + for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) { + subKeys.add(mirroredRepo.getId()); + } + StringDigestUtil sha1 = StringDigestUtil.sha1(); + sha1.update(context); + for (String subKey : subKeys) { + sha1.update(subKey); + } + buffer.append(sha1.digest()); + key = buffer.toString(); + } else { + key = idToPathSegment(repository); + } + return key; } /** - * Returns new instance of function backed by cached or uncached (if session has no cache set) - * {@link #remoteRepositoryUniqueId(RemoteRepository)} method call. - */ - @SuppressWarnings("unchecked") - private static Function cachedRemoteRepositoryUniqueIdFunction( - RepositorySystemSession session) { - if (session.getCache() != null) { - return repository -> ((ConcurrentHashMap) session.getCache() - .computeIfAbsent( - session, - RepositoryIdHelper.class.getSimpleName() + "-remoteRepositoryUniqueIdCache", - ConcurrentHashMap::new)) - .computeIfAbsent(repository, id -> remoteRepositoryUniqueId(repository)); - } else { - return RepositoryIdHelper::remoteRepositoryUniqueId; // uncached + * Globally unique {@code repositoryKey} function. + * + * @since 2.0.14 + **/ + public static String globallyUniqueRepositoryKey(RemoteRepository repository, String context) { + String id = idToPathSegment(repository); + String description = remoteRepositoryDescription(repository); + if (context != null && !context.isEmpty()) { + description += context; } + return id + "-" + StringDigestUtil.sha1(description); } /** @@ -84,6 +99,31 @@ private static Function cachedRemoteRepositoryUniqueId * This method is costly, so should be invoked sparingly, or cache results if needed. */ public static String remoteRepositoryUniqueId(RemoteRepository repository) { + return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(remoteRepositoryDescription(repository)); + } + + /** + * This method returns the passed in {@link ArtifactRepository#getId()} value, modifying it if needed, making sure that + * returned repository ID is "path segment" safe. Ideally, this method should never modify repository ID, as + * Maven validation prevents use of illegal FS characters in them, but we found in Maven Central several POMs that + * define remote repositories with illegal FS characters in their ID. + */ + private static String idToPathSegment(ArtifactRepository repository) { + if (repository instanceof RemoteRepository) { + return PathUtils.stringToPathSegment(repository.getId()); + } else { + return repository.getId(); + } + } + + /** + * Creates unique string for given {@link RemoteRepository}. Ignores following properties: + *
    + *
  • {@link RemoteRepository#getAuthentication()}
  • + *
  • {@link RemoteRepository#getProxy()}
  • + *
+ */ + private static String remoteRepositoryDescription(RemoteRepository repository) { StringBuilder buffer = new StringBuilder(256); buffer.append(repository.getId()); buffer.append(" (").append(repository.getUrl()); @@ -105,7 +145,7 @@ public static String remoteRepositoryUniqueId(RemoteRepository repository) { if (!repository.getMirroredRepositories().isEmpty()) { buffer.append(", mirrorOf("); for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) { - buffer.append(remoteRepositoryUniqueId(mirroredRepo)); + buffer.append(remoteRepositoryDescription(mirroredRepo)); } buffer.append(")"); } @@ -113,55 +153,6 @@ public static String remoteRepositoryUniqueId(RemoteRepository repository) { buffer.append(", blocked"); } buffer.append(")"); - return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(buffer.toString()); - } - - /** - * Returns same instance of (session cached) function for session. - */ - @SuppressWarnings("unchecked") - public static Function cachedIdToPathSegment(RepositorySystemSession session) { - requireNonNull(session, "session"); - return (Function) session.getData() - .computeIfAbsent( - RepositoryIdHelper.class.getSimpleName() + "-idToPathSegmentFunction", - () -> cachedIdToPathSegmentFunction(session)); - } - - /** - * Returns new instance of function backed by cached or uncached (if session has no cache set) - * {@link #idToPathSegment(ArtifactRepository)} method call. - */ - @SuppressWarnings("unchecked") - private static Function cachedIdToPathSegmentFunction(RepositorySystemSession session) { - if (session.getCache() != null) { - return repository -> ((ConcurrentHashMap) session.getCache() - .computeIfAbsent( - session, - RepositoryIdHelper.class.getSimpleName() + "-idToPathSegmentCache", - ConcurrentHashMap::new)) - .computeIfAbsent(repository.getId(), id -> idToPathSegment(repository)); - } else { - return RepositoryIdHelper::idToPathSegment; // uncached - } - } - - /** - * This method returns the passed in {@link ArtifactRepository#getId()} value, modifying it if needed, making sure that - * returned repository ID is "path segment" safe. Ideally, this method should never modify repository ID, as - * Maven validation prevents use of illegal FS characters in them, but we found in Maven Central several POMs that - * define remote repositories with illegal FS characters in their ID. - *

- * This method is simplistic on purpose, and if frequently used, best if results are cached (per session), - * see {@link #cachedIdToPathSegment(RepositorySystemSession)} method. - * - * @see #cachedIdToPathSegment(RepositorySystemSession) - */ - private static String idToPathSegment(ArtifactRepository repository) { - if (repository instanceof RemoteRepository) { - return PathUtils.stringToPathSegment(repository.getId()); - } else { - return repository.getId(); - } + return buffer.toString(); } } From d2d4a5a6cf00b5c27f8279ce08c37d8b6181e4a8 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 20 Nov 2025 18:51:45 +0100 Subject: [PATCH 07/10] Last bits --- .../aether/repository/LocalRepository.java | 15 ++++--- .../repository/WorkspaceRepository.java | 9 +++-- .../resolver/examples/resolver/Resolver.java | 3 +- .../examples/resolver/ResolverDemo.java | 12 +++--- ...DefaultLocalPathPrefixComposerFactory.java | 2 - ...EnhancedLocalRepositoryManagerFactory.java | 22 +++++++--- ...LocalPathPrefixComposerFactorySupport.java | 2 - .../impl/SimpleLocalRepositoryManager.java | 5 --- .../SimpleLocalRepositoryManagerFactory.java | 5 +-- .../eclipse/aether/internal/impl/Utils.java | 14 +++++++ .../FileTrustedChecksumsSourceSupport.java | 2 +- ...SparseDirectoryTrustedChecksumsSource.java | 6 +-- .../SummaryFileTrustedChecksumsSource.java | 6 +-- .../GroupIdRemoteRepositoryFilterSource.java | 4 +- .../PrefixesRemoteRepositoryFilterSource.java | 7 ++-- .../EnhancedLocalRepositoryManagerTest.java | 4 +- ...hancedSplitLocalRepositoryManagerTest.java | 4 +- .../SimpleLocalRepositoryManagerTest.java | 4 +- .../util/repository/RepositoryIdHelper.java | 40 +++++++++---------- .../repository/RepositoryIdHelperTest.java | 29 +------------- 20 files changed, 91 insertions(+), 104 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java index 5e7b24c71..6c586c572 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/LocalRepository.java @@ -37,14 +37,19 @@ public final class LocalRepository implements ArtifactRepository { private final String type; + private final int hashCode; + /** * Creates a new local repository with the specified base directory and unknown type. * * @param basedir The base directory of the repository, may be {@code null}. + * @deprecated Use {@link LocalRepository(Path)} instead. */ + @Deprecated public LocalRepository(String basedir) { this.basePath = Paths.get(RepositoryUriUtils.toUri(basedir)).toAbsolutePath(); this.type = ""; + this.hashCode = Objects.hash(this.basePath, this.type); } /** @@ -100,6 +105,7 @@ public LocalRepository(File basedir, String type) { public LocalRepository(Path basePath, String type) { this.basePath = basePath; this.type = (type != null) ? type : ""; + this.hashCode = Objects.hash(this.basePath, this.type); } @Override @@ -154,13 +160,6 @@ public boolean equals(Object obj) { @Override public int hashCode() { - int hash = 17; - hash = hash * 31 + hash(basePath); - hash = hash * 31 + hash(type); - return hash; - } - - private static int hash(Object obj) { - return obj != null ? obj.hashCode() : 0; + return hashCode; } } diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java index a922c0b07..9b0b44021 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/WorkspaceRepository.java @@ -18,6 +18,7 @@ */ package org.eclipse.aether.repository; +import java.util.Objects; import java.util.UUID; /** @@ -33,6 +34,8 @@ public final class WorkspaceRepository implements ArtifactRepository { private final Object key; + private final int hashCode; + /** * Creates a new workspace repository of type {@code "workspace"} and a random key. */ @@ -59,6 +62,7 @@ public WorkspaceRepository(String type) { public WorkspaceRepository(String type, Object key) { this.type = (type != null) ? type : ""; this.key = (key != null) ? key : UUID.randomUUID().toString().replace("-", ""); + this.hashCode = Objects.hash(type, key); } public String getContentType() { @@ -100,9 +104,6 @@ public boolean equals(Object obj) { @Override public int hashCode() { - int hash = 17; - hash = hash * 31 + getKey().hashCode(); - hash = hash * 31 + getContentType().hashCode(); - return hash; + return hashCode; } } diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/Resolver.java b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/Resolver.java index b44f4011b..8b5e308f7 100644 --- a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/Resolver.java +++ b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/Resolver.java @@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import org.apache.maven.resolver.examples.util.Booter; import org.eclipse.aether.RepositorySystem; @@ -55,7 +56,7 @@ public class Resolver { private final LocalRepository localRepository; - public Resolver(String[] args, String remoteRepository, String localRepository) { + public Resolver(String[] args, String remoteRepository, Path localRepository) { this.args = args; this.remoteRepository = remoteRepository; this.repositorySystem = Booter.newRepositorySystem(Booter.selectFactory(args)); diff --git a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/ResolverDemo.java b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/ResolverDemo.java index 7c0e70a46..eb8193bbd 100644 --- a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/ResolverDemo.java +++ b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/resolver/ResolverDemo.java @@ -19,6 +19,7 @@ package org.apache.maven.resolver.examples.resolver; import java.io.File; +import java.nio.file.Paths; import java.util.List; import org.eclipse.aether.artifact.Artifact; @@ -37,7 +38,8 @@ public static void main(String[] args) throws Exception { System.out.println("------------------------------------------------------------"); System.out.println(ResolverDemo.class.getSimpleName()); - Resolver resolver = new Resolver(args, "https://repo.maven.apache.org/maven2/", "target/resolver-demo-repo"); + Resolver resolver = + new Resolver(args, "https://repo.maven.apache.org/maven2/", Paths.get("target/resolver-demo-repo")); ResolverResult result = resolver.resolve("junit", "junit", "4.13.2"); System.out.println("Result:"); @@ -47,8 +49,8 @@ public static void main(String[] args) throws Exception { } public void resolve(String[] args) throws DependencyResolutionException { - Resolver resolver = - new Resolver(args, "http://localhost:8081/nexus/content/groups/public", "target/aether-repo"); + Resolver resolver = new Resolver( + args, "http://localhost:8081/nexus/content/groups/public", Paths.get("target/aether-repo")); ResolverResult result = resolver.resolve("com.mycompany.app", "super-app", "1.0"); @@ -66,8 +68,8 @@ public void resolve(String[] args) throws DependencyResolutionException { } public void installAndDeploy(String[] args) throws InstallationException, DeploymentException { - Resolver resolver = - new Resolver(args, "http://localhost:8081/nexus/content/groups/public", "target/aether-repo"); + Resolver resolver = new Resolver( + args, "http://localhost:8081/nexus/content/groups/public", Paths.get("target/aether-repo")); Artifact artifact = new DefaultArtifact("com.mycompany.super", "super-core", "jar", "0.1-SNAPSHOT"); artifact = artifact.setFile(new File("jar-from-whatever-process.jar")); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java index 08defd8d6..74c488dda 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactory.java @@ -22,10 +22,8 @@ import javax.inject.Singleton; import java.util.function.BiFunction; -import java.util.function.Function; import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.RemoteRepository; import static org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManagerFactory.repositoryKeyFunction; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java index c4d030bdd..51f01fd3b 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java @@ -22,12 +22,12 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.function.BiFunction; -import java.util.function.Function; import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.NoLocalRepositoryManagerException; @@ -96,11 +96,21 @@ public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryMan * * @since 2.0.14 */ + @SuppressWarnings("unchecked") static BiFunction repositoryKeyFunction(RepositorySystemSession session) { - boolean globallyUniqueRepositoryKeys = ConfigUtils.getBoolean( - session, DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS); - if (globallyUniqueRepositoryKeys) { - return RepositoryIdHelper::globallyUniqueRepositoryKey; + if (ConfigUtils.getBoolean( + session, DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS)) { + // this is expensive method; cache it in session (repo -> context -> ID) + return (repository, context) -> ((ConcurrentMap>) + session.getData() + .computeIfAbsent( + EnhancedLocalRepositoryManagerFactory.class.getName() + + ".repositoryKeyFunction", + ConcurrentHashMap::new)) + .computeIfAbsent(repository, k1 -> new ConcurrentHashMap<>()) + .computeIfAbsent( + context == null ? "" : context, + k2 -> RepositoryIdHelper.globallyUniqueRepositoryKey(repository, context)); } else { return RepositoryIdHelper::simpleRepositoryKey; } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java index e22fcc801..f26c329aa 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java @@ -19,12 +19,10 @@ package org.eclipse.aether.internal.impl; import java.util.function.BiFunction; -import java.util.function.Function; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.metadata.Metadata; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.util.ConfigUtils; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java index 82c1f0ad7..f1da03db9 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManager.java @@ -21,15 +21,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; -import java.util.SortedSet; -import java.util.TreeSet; import java.util.function.BiFunction; -import java.util.function.Function; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.metadata.Metadata; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.LocalArtifactRegistration; import org.eclipse.aether.repository.LocalArtifactRequest; import org.eclipse.aether.repository.LocalArtifactResult; @@ -39,7 +35,6 @@ import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.util.StringDigestUtil; import static java.util.Objects.requireNonNull; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java index 3e02825e5..4be3c2833 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java @@ -62,10 +62,7 @@ public LocalRepositoryManager newInstance(RepositorySystemSession session, Local if ("".equals(repository.getContentType()) || "simple".equals(repository.getContentType())) { return new SimpleLocalRepositoryManager( - repository.getBasePath(), - "simple", - localPathComposer, - RepositoryIdHelper::simpleRepositoryKey); + repository.getBasePath(), "simple", localPathComposer, RepositoryIdHelper::simpleRepositoryKey); } else { throw new NoLocalRepositoryManagerException(repository); } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java index 87cb48ae1..d869a12dd 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; @@ -31,6 +33,7 @@ import org.eclipse.aether.impl.OfflineController; import org.eclipse.aether.installation.InstallRequest; import org.eclipse.aether.metadata.Metadata; +import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ResolutionErrorPolicy; import org.eclipse.aether.resolution.ResolutionErrorPolicyRequest; @@ -39,6 +42,7 @@ import org.eclipse.aether.spi.artifact.generator.ArtifactGenerator; import org.eclipse.aether.spi.artifact.generator.ArtifactGeneratorFactory; import org.eclipse.aether.transfer.RepositoryOfflineException; +import org.eclipse.aether.util.repository.RepositoryIdHelper; /** * Internal utility methods. @@ -207,4 +211,14 @@ public static void checkOffline( offlineController.checkOffline(session, repository); } } + + /** + * Shared and cached {@link RepositoryIdHelper#idToPathSegment(ArtifactRepository)} method, + */ + @SuppressWarnings("unchecked") + public static String cachedIdToPathSegment(RepositorySystemSession session, ArtifactRepository artifactRepository) { + return ((ConcurrentMap) session.getData() + .computeIfAbsent(Utils.class.getName() + ".cachedIdToPathSegment", ConcurrentHashMap::new)) + .computeIfAbsent(artifactRepository, RepositoryIdHelper::idToPathSegment); + } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java index fc417884c..5657a585b 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java @@ -53,7 +53,7 @@ * * @since 1.9.0 */ -abstract class FileTrustedChecksumsSourceSupport implements TrustedChecksumsSource { +public abstract class FileTrustedChecksumsSourceSupport implements TrustedChecksumsSource { protected static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "trustedChecksumsSource."; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java index 1d6af287b..6162b6c95 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java @@ -34,11 +34,11 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.internal.impl.LocalPathComposer; +import org.eclipse.aether.internal.impl.Utils; import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; import org.eclipse.aether.spi.io.ChecksumProcessor; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -134,7 +134,7 @@ protected Map doGetTrustedArtifactChecksums( Path checksumPath = basedir.resolve(calculateArtifactPath( originAware, artifact, - RepositoryIdHelper.cachedIdToPathSegment(session).apply(artifactRepository), + Utils.cachedIdToPathSegment(session, artifactRepository), checksumAlgorithmFactory)); if (!Files.isRegularFile(checksumPath)) { @@ -167,7 +167,7 @@ protected Writer doGetTrustedArtifactChecksumsWriter(RepositorySystemSession ses return new SparseDirectoryWriter( getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, true), isOriginAware(session), - RepositoryIdHelper.cachedIdToPathSegment(session)); + r -> Utils.cachedIdToPathSegment(session, r)); } private String calculateArtifactPath( diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java index 85c9be19f..b6827c5c9 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java @@ -43,11 +43,11 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.impl.RepositorySystemLifecycle; import org.eclipse.aether.internal.impl.LocalPathComposer; +import org.eclipse.aether.internal.impl.Utils; import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; import org.eclipse.aether.spi.io.PathProcessor; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -177,7 +177,7 @@ protected Map doGetTrustedArtifactChecksums( Path summaryFile = summaryFile( basedir, originAware, - RepositoryIdHelper.cachedIdToPathSegment(session).apply(artifactRepository), + Utils.cachedIdToPathSegment(session, artifactRepository), checksumAlgorithmFactory.getFileExtension()); ConcurrentHashMap algorithmChecksums = checksums.computeIfAbsent(summaryFile, f -> loadProvidedChecksums(summaryFile)); @@ -199,7 +199,7 @@ protected Writer doGetTrustedArtifactChecksumsWriter(RepositorySystemSession ses checksums, getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, true), isOriginAware(session), - RepositoryIdHelper.cachedIdToPathSegment(session)); + r -> Utils.cachedIdToPathSegment(session, r)); } /** 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 3d6643876..dd4699331 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 @@ -42,6 +42,7 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.impl.RepositorySystemLifecycle; +import org.eclipse.aether.internal.impl.Utils; import org.eclipse.aether.internal.impl.filter.ruletree.GroupTree; import org.eclipse.aether.metadata.Metadata; import org.eclipse.aether.repository.RemoteRepository; @@ -50,7 +51,6 @@ import org.eclipse.aether.spi.io.PathProcessor; import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -249,7 +249,7 @@ private Path ruleFile(RepositorySystemSession session, RemoteRepository remoteRe return ruleFiles(session).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) + + Utils.cachedIdToPathSegment(session, remoteRepository) + GROUP_ID_FILE_SUFFIX)); } 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 9a4b2c301..117e621c3 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 @@ -36,6 +36,7 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.impl.MetadataResolver; import org.eclipse.aether.impl.RemoteRepositoryManager; +import org.eclipse.aether.internal.impl.Utils; import org.eclipse.aether.internal.impl.filter.prefixes.PrefixesSource; import org.eclipse.aether.internal.impl.filter.ruletree.PrefixTree; import org.eclipse.aether.metadata.DefaultMetadata; @@ -49,7 +50,6 @@ import org.eclipse.aether.spi.connector.layout.RepositoryLayoutProvider; import org.eclipse.aether.transfer.NoRepositoryLayoutException; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -318,9 +318,8 @@ private PrefixTree loadPrefixTree( private Path resolvePrefixesFromLocalConfiguration( RepositorySystemSession session, Path baseDir, RemoteRepository remoteRepository) { - Path filePath = baseDir.resolve(PREFIXES_FILE_PREFIX - + RepositoryIdHelper.cachedIdToPathSegment(session).apply(remoteRepository) - + PREFIXES_FILE_SUFFIX); + Path filePath = baseDir.resolve( + PREFIXES_FILE_PREFIX + Utils.cachedIdToPathSegment(session, remoteRepository) + PREFIXES_FILE_SUFFIX); if (Files.isReadable(filePath)) { return filePath; } else { diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerTest.java index 7d8a296cc..25c6af747 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerTest.java @@ -33,13 +33,13 @@ import org.eclipse.aether.metadata.DefaultMetadata; import org.eclipse.aether.metadata.Metadata; import org.eclipse.aether.metadata.Metadata.Nature; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.LocalArtifactRegistration; import org.eclipse.aether.repository.LocalArtifactRequest; import org.eclipse.aether.repository.LocalArtifactResult; import org.eclipse.aether.repository.LocalMetadataRequest; import org.eclipse.aether.repository.LocalMetadataResult; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -108,7 +108,7 @@ protected EnhancedLocalRepositoryManager getManager() { return new EnhancedLocalRepositoryManager( basedir.toPath(), new DefaultLocalPathComposer(), - ArtifactRepository::getId, + RepositoryIdHelper::simpleRepositoryKey, "_remote.repositories", trackingFileManager, new DefaultLocalPathPrefixComposerFactory().createComposer(session)); diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedSplitLocalRepositoryManagerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedSplitLocalRepositoryManagerTest.java index 4a78ea302..dbf37e80c 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedSplitLocalRepositoryManagerTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/EnhancedSplitLocalRepositoryManagerTest.java @@ -20,8 +20,8 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -34,7 +34,7 @@ protected EnhancedLocalRepositoryManager getManager() { return new EnhancedLocalRepositoryManager( basedir.toPath(), new DefaultLocalPathComposer(), - ArtifactRepository::getId, + RepositoryIdHelper::simpleRepositoryKey, "_remote.repositories", trackingFileManager, new DefaultLocalPathPrefixComposerFactory().createComposer(session)); diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerTest.java index fbe240992..5bd674136 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerTest.java @@ -26,10 +26,10 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.internal.test.util.TestFileUtils; import org.eclipse.aether.internal.test.util.TestUtils; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.LocalArtifactRequest; import org.eclipse.aether.repository.LocalArtifactResult; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -50,7 +50,7 @@ public class SimpleLocalRepositoryManagerTest { @BeforeEach void setup() throws IOException { manager = new SimpleLocalRepositoryManager( - basedir.toPath(), "simple", new DefaultLocalPathComposer(), ArtifactRepository::getId); + basedir.toPath(), "simple", new DefaultLocalPathComposer(), RepositoryIdHelper::simpleRepositoryKey); session = TestUtils.newSession(); } 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 4afdce3e7..0722a458a 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 @@ -20,24 +20,20 @@ import java.util.SortedSet; import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.BiFunction; -import java.util.function.Function; -import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.util.PathUtils; import org.eclipse.aether.util.StringDigestUtil; -import static java.util.Objects.requireNonNull; - /** - * Helper class for {@link ArtifactRepository#getId()} handling. This class provides helper function (cached or uncached) - * to get id of repository as it was originally envisioned: as path safe. While POMs are validated by Maven, there are - * POMs out there that somehow define repositories with unsafe characters in their id. The problem affects mostly + * Helper class for {@link ArtifactRepository#getId()} handling. This class provides helper methods + * to get id of repository as it was originally envisioned: as path safe, unique, etc. While POMs are validated by Maven, + * there are POMs out there that somehow define repositories with unsafe characters in their id. The problem affects mostly * {@link RemoteRepository} instances, as all other implementations have fixed ids that are path safe. + *

+ * Important: multiple of these provided methods are not trivial processing-wise, and some sort of + * caching is warmly recommended. * * @see PathUtils * @since 2.0.11 @@ -49,6 +45,7 @@ private RepositoryIdHelper() {} * Simple {@code repositoryKey} function (classic). Returns {@link RemoteRepository#getId()}, unless * {@link RemoteRepository#isRepositoryManager()} returns {@code true}, in which case this method creates * unique identifier based on ID and current configuration of the remote repository (as it may change). + * This was the default method in Maven 3. * * @since 2.0.14 **/ @@ -76,25 +73,26 @@ public static String simpleRepositoryKey(RemoteRepository repository, String con } /** - * Globally unique {@code repositoryKey} function. + * Globally unique {@code repositoryKey} function. This repository key method returns same results as + * {@link #remoteRepositoryUniqueId(RemoteRepository)} if parameter context is {@code null} or empty string. * + * @see #remoteRepositoryUniqueId(RemoteRepository) * @since 2.0.14 **/ public static String globallyUniqueRepositoryKey(RemoteRepository repository, String context) { - String id = idToPathSegment(repository); String description = remoteRepositoryDescription(repository); if (context != null && !context.isEmpty()) { description += context; } - return id + "-" + StringDigestUtil.sha1(description); + return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(description); } /** - * Creates unique repository id for given {@link RemoteRepository}. For Maven Central this method will return - * string "central", while for any other remote repository it will return string created as - * {@code $(repository.id)-sha1(repository-aspects)}. The key material contains all relevant aspects - * of remote repository, so repository with same ID even if just policy changes (enabled/disabled), will map to - * different string id. The checksum and update policies are not participating in key creation. + * Creates unique repository id for given {@link RemoteRepository}. + * For any remote repository it will return string created as {@code $(repository.id)-sha1(repository-aspects)}. + * The key material contains all relevant aspects of remote repository, so repository with same ID even if just + * policy changes (enabled/disabled), will map to 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. *

@@ -113,7 +111,7 @@ public static String remoteRepositoryUniqueId(RemoteRepository repository) { * Maven validation prevents use of illegal FS characters in them, but we found in Maven Central several POMs that * define remote repositories with illegal FS characters in their ID. */ - private static String idToPathSegment(ArtifactRepository repository) { + public static String idToPathSegment(ArtifactRepository repository) { if (repository instanceof RemoteRepository) { return PathUtils.stringToPathSegment(repository.getId()); } else { @@ -128,7 +126,7 @@ private static String idToPathSegment(ArtifactRepository repository) { *

  • {@link RemoteRepository#getProxy()}
  • * */ - private static String remoteRepositoryDescription(RemoteRepository repository) { + public static String remoteRepositoryDescription(RemoteRepository repository) { StringBuilder buffer = new StringBuilder(256); buffer.append(repository.getId()); buffer.append(" (").append(repository.getUrl()); @@ -157,7 +155,7 @@ private static String remoteRepositoryDescription(RemoteRepository repository) { if (repository.isBlocked()) { buffer.append(", blocked"); } - buffer.append(")"); + buffer.append(", ").append(repository.getIntent().name()).append(")"); return buffer.toString(); } } diff --git a/maven-resolver-util/src/test/java/org/eclipse/aether/util/repository/RepositoryIdHelperTest.java b/maven-resolver-util/src/test/java/org/eclipse/aether/util/repository/RepositoryIdHelperTest.java index bdbc15c1e..f9c10ee72 100644 --- a/maven-resolver-util/src/test/java/org/eclipse/aether/util/repository/RepositoryIdHelperTest.java +++ b/maven-resolver-util/src/test/java/org/eclipse/aether/util/repository/RepositoryIdHelperTest.java @@ -28,15 +28,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertSame; public class RepositoryIdHelperTest { @Test - void caching() { + void idToPathSegment() { DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(s -> false); session.setCache(new DefaultRepositoryCache()); // session has cache set - Function safeId = RepositoryIdHelper.cachedIdToPathSegment(session); + Function safeId = RepositoryIdHelper::idToPathSegment; RemoteRepository good = new RemoteRepository.Builder("good", "default", "https://somewhere.com").build(); RemoteRepository bad = new RemoteRepository.Builder("bad/id", "default", "https://somewhere.com").build(); @@ -44,33 +42,10 @@ void caching() { String goodId = good.getId(); String goodFixedId = safeId.apply(good); assertEquals(goodId, goodFixedId); - assertSame(goodFixedId, safeId.apply(good)); String badId = bad.getId(); String badFixedId = safeId.apply(bad); assertNotEquals(badId, badFixedId); assertEquals("bad-SLASH-id", badFixedId); - assertSame(badFixedId, safeId.apply(bad)); - } - - @Test - void nonCaching() { - DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(s -> false); - session.setCache(null); // session has no cache set - Function safeId = RepositoryIdHelper.cachedIdToPathSegment(session); - - RemoteRepository good = new RemoteRepository.Builder("good", "default", "https://somewhere.com").build(); - RemoteRepository bad = new RemoteRepository.Builder("bad/id", "default", "https://somewhere.com").build(); - - String goodId = good.getId(); - String goodFixedId = safeId.apply(good); - assertEquals(goodId, goodFixedId); - assertNotSame(goodFixedId, safeId.apply(good)); - - String badId = bad.getId(); - String badFixedId = safeId.apply(bad); - assertNotEquals(badId, badFixedId); - assertEquals("bad-SLASH-id", badFixedId); - assertNotSame(badFixedId, safeId.apply(bad)); } } From e9cf9319c48fc0416b11458feb3b36df403d4a33 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 21 Nov 2025 13:16:16 +0100 Subject: [PATCH 08/10] Fix helper methods, add TODO --- .../util/repository/RepositoryIdHelper.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) 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 0722a458a..cdad560d7 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 @@ -44,13 +44,13 @@ private RepositoryIdHelper() {} /** * Simple {@code repositoryKey} function (classic). Returns {@link RemoteRepository#getId()}, unless * {@link RemoteRepository#isRepositoryManager()} returns {@code true}, in which case this method creates - * unique identifier based on ID and current configuration of the remote repository (as it may change). - * This was the default method in Maven 3. + * unique identifier based on ID and current configuration of the remote repository and context. + *

    + * This was the default {@code repositoryKey} method in Maven 3. * * @since 2.0.14 **/ public static String simpleRepositoryKey(RemoteRepository repository, String context) { - String key; if (repository.isRepositoryManager()) { StringBuilder buffer = new StringBuilder(128); buffer.append(idToPathSegment(repository)); @@ -65,23 +65,28 @@ public static String simpleRepositoryKey(RemoteRepository repository, String con sha1.update(subKey); } buffer.append(sha1.digest()); - key = buffer.toString(); + return buffer.toString(); } else { - key = idToPathSegment(repository); + return idToPathSegment(repository); } - return key; } /** - * Globally unique {@code repositoryKey} function. This repository key method returns same results as + * Globally unique {@code repositoryKey} function. This method creates unique identifier based on ID and current + * configuration of the remote repository. If {@link RemoteRepository#isRepositoryManager()} returns {@code true}, + * the passed in {@code context} string is factored in as well. This repository key method returns same results as * {@link #remoteRepositoryUniqueId(RemoteRepository)} if parameter context is {@code null} or empty string. + *

    + * Important: this repository key can be considered "stable" for normal remote repositories (where only + * ID and URL matters). But, for mirror repositories, the key will change if mirror members change. + * TODO: reconsider this? * * @see #remoteRepositoryUniqueId(RemoteRepository) * @since 2.0.14 **/ public static String globallyUniqueRepositoryKey(RemoteRepository repository, String context) { String description = remoteRepositoryDescription(repository); - if (context != null && !context.isEmpty()) { + if (repository.isRepositoryManager() && context != null && !context.isEmpty()) { description += context; } return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(description); @@ -124,6 +129,7 @@ public static String idToPathSegment(ArtifactRepository repository) { *

      *
    • {@link RemoteRepository#getAuthentication()}
    • *
    • {@link RemoteRepository#getProxy()}
    • + *
    • {@link RemoteRepository#getIntent()}
    • *
    */ public static String remoteRepositoryDescription(RemoteRepository repository) { @@ -155,7 +161,7 @@ public static String remoteRepositoryDescription(RemoteRepository repository) { if (repository.isBlocked()) { buffer.append(", blocked"); } - buffer.append(", ").append(repository.getIntent().name()).append(")"); + buffer.append(")"); return buffer.toString(); } } From 8add1c7e396661b2074fbdf3f9590d2fa88b88a7 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 21 Nov 2025 13:31:20 +0100 Subject: [PATCH 09/10] Use cache for cache --- .../EnhancedLocalRepositoryManagerFactory.java | 18 ++++++++++++------ .../eclipse/aether/internal/impl/Utils.java | 11 ++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java index 51f01fd3b..9d111053b 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/EnhancedLocalRepositoryManagerFactory.java @@ -98,21 +98,27 @@ public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryMan */ @SuppressWarnings("unchecked") static BiFunction repositoryKeyFunction(RepositorySystemSession session) { - if (ConfigUtils.getBoolean( - session, DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS)) { - // this is expensive method; cache it in session (repo -> context -> ID) + final boolean globallyUniqueRepositoryKeys = ConfigUtils.getBoolean( + session, DEFAULT_GLOBALLY_UNIQUE_REPOSITORY_KEYS, CONFIG_PROP_GLOBALLY_UNIQUE_REPOSITORY_KEYS); + if (session.getCache() != null) { + // both are expensive methods; cache it in session (repo -> context -> ID) return (repository, context) -> ((ConcurrentMap>) - session.getData() + session.getCache() .computeIfAbsent( + session, EnhancedLocalRepositoryManagerFactory.class.getName() + ".repositoryKeyFunction", ConcurrentHashMap::new)) .computeIfAbsent(repository, k1 -> new ConcurrentHashMap<>()) .computeIfAbsent( context == null ? "" : context, - k2 -> RepositoryIdHelper.globallyUniqueRepositoryKey(repository, context)); + k2 -> globallyUniqueRepositoryKeys + ? RepositoryIdHelper.globallyUniqueRepositoryKey(repository, context) + : RepositoryIdHelper.simpleRepositoryKey(repository, context)); } else { - return RepositoryIdHelper::simpleRepositoryKey; + return globallyUniqueRepositoryKeys + ? RepositoryIdHelper::globallyUniqueRepositoryKey + : RepositoryIdHelper::simpleRepositoryKey; } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java index d869a12dd..7b5d6f203 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Utils.java @@ -217,8 +217,13 @@ public static void checkOffline( */ @SuppressWarnings("unchecked") public static String cachedIdToPathSegment(RepositorySystemSession session, ArtifactRepository artifactRepository) { - return ((ConcurrentMap) session.getData() - .computeIfAbsent(Utils.class.getName() + ".cachedIdToPathSegment", ConcurrentHashMap::new)) - .computeIfAbsent(artifactRepository, RepositoryIdHelper::idToPathSegment); + if (session.getCache() != null) { + return ((ConcurrentMap) session.getCache() + .computeIfAbsent( + session, Utils.class.getName() + ".cachedIdToPathSegment", ConcurrentHashMap::new)) + .computeIfAbsent(artifactRepository, RepositoryIdHelper::idToPathSegment); + } else { + return RepositoryIdHelper.idToPathSegment(artifactRepository); + } } } From cdb8c90dae0038927999893217002d33ac1653e9 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 21 Nov 2025 13:39:20 +0100 Subject: [PATCH 10/10] Don't make Simple unusable; cache simple repository key function as well Signed-off-by: Tamas Cservenak --- .../SimpleLocalRepositoryManagerFactory.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java index 4be3c2833..a5975138a 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/SimpleLocalRepositoryManagerFactory.java @@ -22,10 +22,15 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; + import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.NoLocalRepositoryManagerException; +import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory; import org.eclipse.aether.util.repository.RepositoryIdHelper; @@ -54,6 +59,7 @@ public SimpleLocalRepositoryManagerFactory(final LocalPathComposer localPathComp this.localPathComposer = requireNonNull(localPathComposer); } + @SuppressWarnings("unchecked") @Override public LocalRepositoryManager newInstance(RepositorySystemSession session, LocalRepository repository) throws NoLocalRepositoryManagerException { @@ -61,8 +67,21 @@ public LocalRepositoryManager newInstance(RepositorySystemSession session, Local requireNonNull(repository, "repository cannot be null"); if ("".equals(repository.getContentType()) || "simple".equals(repository.getContentType())) { + BiFunction repositoryKeyFunction = + RepositoryIdHelper::simpleRepositoryKey; + if (session.getCache() != null) { + repositoryKeyFunction = (r, c) -> ((ConcurrentMap>) + session.getCache() + .computeIfAbsent( + session, + EnhancedLocalRepositoryManagerFactory.class.getName() + + ".repositoryKeyFunction", + ConcurrentHashMap::new)) + .computeIfAbsent(r, k1 -> new ConcurrentHashMap<>()) + .computeIfAbsent(c == null ? "" : c, k2 -> RepositoryIdHelper.simpleRepositoryKey(r, c)); + } return new SimpleLocalRepositoryManager( - repository.getBasePath(), "simple", localPathComposer, RepositoryIdHelper::simpleRepositoryKey); + repository.getBasePath(), "simple", localPathComposer, repositoryKeyFunction); } else { throw new NoLocalRepositoryManagerException(repository); }