diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java b/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java index 327815fdb..3945a7486 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/ConfigurationProperties.java @@ -149,6 +149,13 @@ public final class ConfigurationProperties { */ public static final String CACHED_PRIORITIES = PREFIX_PRIORITY + "cached"; + /** + * The default caching of priority components if {@link #CACHED_PRIORITIES} isn't set. Default value is {@code true}. + * + * @since 2.0.0 + */ + public static final boolean DEFAULT_CACHED_PRIORITIES = true; + /** * The priority to use for a certain extension class. {@code <class>} can either be the fully qualified * name or the simple name of a class. If the class name ends with Factory that suffix could optionally be left out. @@ -171,13 +178,6 @@ public final class ConfigurationProperties { */ public static final String CLASS_PRIORITIES = PREFIX_PRIORITY + ""; - /** - * The default caching of priority components if {@link #CACHED_PRIORITIES} isn't set. Default value is {@code true}. - * - * @since 2.0.0 - */ - public static final boolean DEFAULT_CACHED_PRIORITIES = true; - /** * A flag indicating whether interaction with the user is allowed. * @@ -560,6 +560,23 @@ public final class ConfigurationProperties { public static final String DEFAULT_REPOSITORY_SYSTEM_DEPENDENCY_VISITOR = REPOSITORY_SYSTEM_DEPENDENCY_VISITOR_LEVELORDER; + /** + * Experimental: Configuration for system-wide "repository key" function. + * Accepted and recommended values: "nid" (default), "nid_hurl" and "ngurk", while "simple" is Maven 3 legacy, + * technically equivalent to "nid". For complete description see enum + * {@code org.eclipse.aether.util.repository.RepositoryIdHelper.RepositoryKeyType} in utils. Warning: + * repository key function affects Resolver fundamentally and may have unexpected results! Only change this + * if you know what you are doing! + * + * @since 2.0.14 + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link java.lang.String} + * @configurationDefaultValue {@link #DEFAULT_REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION} + */ + public static final String REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION = PREFIX_SYSTEM + "repositoryKeyFunction"; + + public static final String DEFAULT_REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION = "nid"; + /** * A flag indicating whether version scheme cache statistics should be printed on JVM shutdown. * This is useful for analyzing cache performance and effectiveness in development and testing scenarios. 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..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 @@ -31,19 +31,25 @@ * the repository. */ public final class LocalRepository implements ArtifactRepository { + public static final String ID = "local"; private final Path basePath; 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); } /** @@ -99,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 @@ -108,7 +115,7 @@ public String getContentType() { @Override public String getId() { - return "local"; + return ID; } /** @@ -153,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/RepositoryKeyFunction.java b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/RepositoryKeyFunction.java new file mode 100644 index 000000000..e44f7cdc9 --- /dev/null +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/RepositoryKeyFunction.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.repository; + +import java.util.function.BiFunction; + +/** + * The repository key function, it produces keys (strings) for given {@link RemoteRepository} instances. + * + * @since 2.0.14 + */ +@FunctionalInterface +public interface RepositoryKeyFunction extends BiFunction { + /** + * Produces a string representing "repository key" for given {@link RemoteRepository} and + * optionally (maybe {@code null}) "context". + * + * @param repository The {@link RemoteRepository}, may not be {@code null}. + * @param context The "context" string, or {@code null}. + * @return The "repository key" string, never {@code null}. + */ + @Override + String apply(RemoteRepository repository, String context); +} 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..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; /** @@ -27,11 +28,14 @@ * 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; private final Object key; + private final int hashCode; + /** * Creates a new workspace repository of type {@code "workspace"} and a random key. */ @@ -58,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() { @@ -65,7 +70,7 @@ public String getContentType() { } public String getId() { - return "workspace"; + return ID; } /** @@ -99,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 2a18ce049..022400667 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 @@ -18,14 +18,15 @@ */ package org.eclipse.aether.internal.impl; +import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import java.util.function.Function; - import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.repository.ArtifactRepository; -import org.eclipse.aether.util.repository.RepositoryIdHelper; +import org.eclipse.aether.repository.RepositoryKeyFunction; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; + +import static java.util.Objects.requireNonNull; /** * Default local path prefix composer factory: it fully reuses {@link LocalPathPrefixComposerFactorySupport} class @@ -36,6 +37,13 @@ @Singleton @Named public final class DefaultLocalPathPrefixComposerFactory extends LocalPathPrefixComposerFactorySupport { + private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory; + + @Inject + public DefaultLocalPathPrefixComposerFactory(RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) { + this.repositoryKeyFunctionFactory = requireNonNull(repositoryKeyFunctionFactory); + } + @Override public LocalPathPrefixComposer createComposer(RepositorySystemSession session) { return new DefaultLocalPathPrefixComposer( @@ -48,7 +56,7 @@ public LocalPathPrefixComposer createComposer(RepositorySystemSession session) { isSplitRemoteRepositoryLast(session), getReleasesPrefix(session), getSnapshotsPrefix(session), - RepositoryIdHelper.cachedIdToPathSegment(session)); + repositoryKeyFunctionFactory.systemRepositoryKeyFunction(session)); } /** @@ -66,7 +74,7 @@ private DefaultLocalPathPrefixComposer( boolean splitRemoteRepositoryLast, String releasesPrefix, String snapshotsPrefix, - Function idToPathSegmentFunction) { + RepositoryKeyFunction repositoryKeyFunction) { super( split, localPrefix, @@ -77,7 +85,7 @@ private DefaultLocalPathPrefixComposer( splitRemoteRepositoryLast, releasesPrefix, snapshotsPrefix, - idToPathSegmentFunction); + repositoryKeyFunction); } } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java index 3ad72aade..fbe35695a 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java @@ -38,8 +38,10 @@ import org.eclipse.aether.repository.Proxy; import org.eclipse.aether.repository.ProxySelector; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryKeyFunction; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.spi.connector.checksum.ChecksumPolicyProvider; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,11 +84,17 @@ public int hashCode() { private final ChecksumPolicyProvider checksumPolicyProvider; + private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory; + @Inject public DefaultRemoteRepositoryManager( - UpdatePolicyAnalyzer updatePolicyAnalyzer, ChecksumPolicyProvider checksumPolicyProvider) { + UpdatePolicyAnalyzer updatePolicyAnalyzer, + ChecksumPolicyProvider checksumPolicyProvider, + RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) { this.updatePolicyAnalyzer = requireNonNull(updatePolicyAnalyzer, "update policy analyzer cannot be null"); this.checksumPolicyProvider = requireNonNull(checksumPolicyProvider, "checksum policy provider cannot be null"); + this.repositoryKeyFunctionFactory = + requireNonNull(repositoryKeyFunctionFactory, "repository key function factory cannot be null"); } @Override @@ -102,6 +110,7 @@ public List aggregateRepositories( return dominantRepositories; } + RepositoryKeyFunction repositoryKeyFunction = repositoryKeyFunctionFactory.systemRepositoryKeyFunction(session); MirrorSelector mirrorSelector = session.getMirrorSelector(); AuthenticationSelector authSelector = session.getAuthenticationSelector(); ProxySelector proxySelector = session.getProxySelector(); @@ -121,15 +130,16 @@ public List aggregateRepositories( } } - String key = getKey(repository); + String key = repositoryKeyFunction.apply(repository, null); for (ListIterator it = result.listIterator(); it.hasNext(); ) { RemoteRepository dominantRepository = it.next(); - if (key.equals(getKey(dominantRepository))) { + if (key.equals(repositoryKeyFunction.apply(dominantRepository, null))) { if (!dominantRepository.getMirroredRepositories().isEmpty() && !repository.getMirroredRepositories().isEmpty()) { - RemoteRepository mergedRepository = mergeMirrors(session, dominantRepository, repository); + RemoteRepository mergedRepository = + mergeMirrors(session, repositoryKeyFunction, dominantRepository, repository); if (mergedRepository != dominantRepository) { it.set(mergedRepository); } @@ -188,21 +198,20 @@ private void logMirror(RepositorySystemSession session, RemoteRepository origina original.getUrl()); } - private String getKey(RemoteRepository repository) { - return repository.getId(); - } - private RemoteRepository mergeMirrors( - RepositorySystemSession session, RemoteRepository dominant, RemoteRepository recessive) { + RepositorySystemSession session, + RepositoryKeyFunction repositoryKeyFunction, + RemoteRepository dominant, + RemoteRepository recessive) { RemoteRepository.Builder merged = null; RepositoryPolicy releases = null, snapshots = null; next: for (RemoteRepository rec : recessive.getMirroredRepositories()) { - String recKey = getKey(rec); + String recKey = repositoryKeyFunction.apply(rec, null); for (RemoteRepository dom : dominant.getMirroredRepositories()) { - if (recKey.equals(getKey(dom))) { + if (recKey.equals(repositoryKeyFunction.apply(dom, null))) { continue next; } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositoryKeyFunctionFactory.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositoryKeyFunctionFactory.java new file mode 100644 index 000000000..4f7a70621 --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositoryKeyFunctionFactory.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.internal.impl; + +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.eclipse.aether.ConfigurationProperties; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryKeyFunction; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; +import org.eclipse.aether.util.ConfigUtils; +import org.eclipse.aether.util.repository.RepositoryIdHelper; + +import static java.util.Objects.requireNonNull; + +@Singleton +@Named +public class DefaultRepositoryKeyFunctionFactory implements RepositoryKeyFunctionFactory { + /** + * Returns system-wide repository key function. + * + * @since 2.0.14 + * @see #repositoryKeyFunction(Class, RepositorySystemSession, String, String) + */ + @Override + public RepositoryKeyFunction systemRepositoryKeyFunction(RepositorySystemSession session) { + return repositoryKeyFunction( + DefaultRepositoryKeyFunctionFactory.class, + session, + ConfigurationProperties.DEFAULT_REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION, + ConfigurationProperties.REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION); + } + + /** + * Method that based on configuration returns the "repository key function". The returned function will be session + * cached if session is equipped with cache, otherwise it will be non cached. Method never returns {@code null}. + * Only the {@code configurationKey} parameter may be {@code null} in which case no configuration lookup happens + * but the {@code defaultValue} is directly used instead. + * + * @since 2.0.14 + */ + @SuppressWarnings("unchecked") + @Override + public RepositoryKeyFunction repositoryKeyFunction( + Class owner, RepositorySystemSession session, String defaultValue, String configurationKey) { + requireNonNull(session); + requireNonNull(defaultValue); + final RepositoryKeyFunction repositoryKeyFunction = RepositoryIdHelper.getRepositoryKeyFunction( + configurationKey != null + ? ConfigUtils.getString(session, defaultValue, configurationKey) + : defaultValue); + if (session.getCache() != null) { + // both are expensive methods; cache it in session (repo -> context -> ID) + return (repository, context) -> ((ConcurrentMap>) + session.getCache() + .computeIfAbsent( + session, + owner.getName() + ".repositoryKeyFunction", + ConcurrentHashMap::new)) + .computeIfAbsent(repository, k1 -> new ConcurrentHashMap<>()) + .computeIfAbsent( + context == null ? "" : context, k2 -> repositoryKeyFunction.apply(repository, context)); + } else { + return 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..dd314fed9 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,16 +27,15 @@ import java.util.Map; import java.util.Objects; import java.util.Properties; -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; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryKeyFunction; import static java.util.Objects.requireNonNull; @@ -72,11 +71,11 @@ class EnhancedLocalRepositoryManager extends SimpleLocalRepositoryManager { EnhancedLocalRepositoryManager( Path basedir, LocalPathComposer localPathComposer, - Function idToPathSegmentFunction, + RepositoryKeyFunction 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 b00f1dff9..e31a493c9 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 @@ -28,8 +28,8 @@ import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.NoLocalRepositoryManagerException; import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.repository.RepositoryIdHelper; import static java.util.Objects.requireNonNull; @@ -66,14 +66,18 @@ public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryMan private final LocalPathPrefixComposerFactory localPathPrefixComposerFactory; + private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory; + @Inject public EnhancedLocalRepositoryManagerFactory( final LocalPathComposer localPathComposer, final TrackingFileManager trackingFileManager, - final LocalPathPrefixComposerFactory localPathPrefixComposerFactory) { + final LocalPathPrefixComposerFactory localPathPrefixComposerFactory, + final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) { this.localPathComposer = requireNonNull(localPathComposer); this.trackingFileManager = requireNonNull(trackingFileManager); this.localPathPrefixComposerFactory = requireNonNull(localPathPrefixComposerFactory); + this.repositoryKeyFunctionFactory = requireNonNull(repositoryKeyFunctionFactory); } @Override @@ -94,7 +98,7 @@ public LocalRepositoryManager newInstance(RepositorySystemSession session, Local return new EnhancedLocalRepositoryManager( repository.getBasePath(), localPathComposer, - RepositoryIdHelper.cachedIdToPathSegment(session), + repositoryKeyFunctionFactory.systemRepositoryKeyFunction(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..a01b377c8 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,13 +18,11 @@ */ package org.eclipse.aether.internal.impl; -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.repository.RepositoryKeyFunction; import org.eclipse.aether.util.ConfigUtils; /** @@ -244,7 +242,7 @@ protected abstract static class LocalPathPrefixComposerSupport implements LocalP protected final String snapshotsPrefix; - protected final Function idToPathSegmentFunction; + protected final RepositoryKeyFunction repositoryKeyFunction; protected LocalPathPrefixComposerSupport( boolean split, @@ -256,7 +254,7 @@ protected LocalPathPrefixComposerSupport( boolean splitRemoteRepositoryLast, String releasesPrefix, String snapshotsPrefix, - Function idToPathSegmentFunction) { + RepositoryKeyFunction repositoryKeyFunction) { this.split = split; this.localPrefix = localPrefix; this.splitLocal = splitLocal; @@ -266,7 +264,7 @@ protected LocalPathPrefixComposerSupport( this.splitRemoteRepositoryLast = splitRemoteRepositoryLast; this.releasesPrefix = releasesPrefix; this.snapshotsPrefix = snapshotsPrefix; - this.idToPathSegmentFunction = idToPathSegmentFunction; + this.repositoryKeyFunction = repositoryKeyFunction; } @Override @@ -288,13 +286,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 +316,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 ea751d41a..0acf1b374 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,14 +21,10 @@ 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.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; @@ -38,7 +34,7 @@ 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 org.eclipse.aether.repository.RepositoryKeyFunction; import static java.util.Objects.requireNonNull; @@ -51,17 +47,17 @@ class SimpleLocalRepositoryManager implements LocalRepositoryManager { private final LocalPathComposer localPathComposer; - private final Function idToPathSegmentFunction; + private final RepositoryKeyFunction repositoryKeyFunction; SimpleLocalRepositoryManager( Path basePath, String type, LocalPathComposer localPathComposer, - Function idToPathSegmentFunction) { + RepositoryKeyFunction 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 @@ -85,7 +81,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 @@ -101,35 +97,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..f45460852 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 @@ -27,6 +27,7 @@ import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.NoLocalRepositoryManagerException; import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.util.repository.RepositoryIdHelper; import static java.util.Objects.requireNonNull; @@ -41,17 +42,22 @@ public class SimpleLocalRepositoryManagerFactory implements LocalRepositoryManag private float priority; private final LocalPathComposer localPathComposer; + private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory; /** * No-arg constructor, as "simple" local repository is meant mainly for use in tests. */ public SimpleLocalRepositoryManagerFactory() { this.localPathComposer = new DefaultLocalPathComposer(); + this.repositoryKeyFunctionFactory = new DefaultRepositoryKeyFunctionFactory(); } @Inject - public SimpleLocalRepositoryManagerFactory(final LocalPathComposer localPathComposer) { + public SimpleLocalRepositoryManagerFactory( + final LocalPathComposer localPathComposer, + final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) { this.localPathComposer = requireNonNull(localPathComposer); + this.repositoryKeyFunctionFactory = requireNonNull(repositoryKeyFunctionFactory); } @Override @@ -65,7 +71,11 @@ public LocalRepositoryManager newInstance(RepositorySystemSession session, Local repository.getBasePath(), "simple", localPathComposer, - RepositoryIdHelper.cachedIdToPathSegment(session)); + repositoryKeyFunctionFactory.repositoryKeyFunction( + SimpleLocalRepositoryManagerFactory.class, + session, + RepositoryIdHelper.RepositoryKeyType.SIMPLE.name(), + null)); } else { throw new NoLocalRepositoryManagerException(repository); } 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..8f052b92b 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 @@ -28,8 +28,10 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.repository.ArtifactRepository; +import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.spi.checksums.TrustedChecksumsSource; import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.util.DirectoryUtils; import static java.util.Objects.requireNonNull; @@ -53,10 +55,31 @@ * * @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."; + /** + * Experimental: Configuration for "repository key" function. + * Note: repository key functions other than "nid" produce repository keys will be way different + * that those produced with previous versions or without this option enabled. Checksum source uses this key + * function to lay down and look up files to use in sources. + * + * @since 2.0.14 + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link java.lang.String} + * @configurationDefaultValue {@link #DEFAULT_REPOSITORY_KEY_FUNCTION} + */ + public static final String CONFIG_PROP_REPOSITORY_KEY_FUNCTION = CONFIG_PROPS_PREFIX + "repositoryKeyFunction"; + + public static final String DEFAULT_REPOSITORY_KEY_FUNCTION = "nid"; + + private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory; + + protected FileTrustedChecksumsSourceSupport(RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) { + this.repositoryKeyFunctionFactory = requireNonNull(repositoryKeyFunctionFactory); + } + /** * This implementation will call into underlying code only if enabled, and will enforce non-{@code null} return * value. In worst case, empty map should be returned, meaning "no trusted checksums available". @@ -131,4 +154,23 @@ protected Path getBasedir( throw new UncheckedIOException(e); } } + + /** + * Returns repository key to be used on file system layout. + * + * @since 2.0.14 + */ + protected String repositoryKey(RepositorySystemSession session, ArtifactRepository artifactRepository) { + if (artifactRepository instanceof RemoteRepository) { + return repositoryKeyFunctionFactory + .repositoryKeyFunction( + FileTrustedChecksumsSourceSupport.class, + session, + DEFAULT_REPOSITORY_KEY_FUNCTION, + CONFIG_PROP_REPOSITORY_KEY_FUNCTION) + .apply((RemoteRepository) artifactRepository, null); + } else { + return artifactRepository.getId(); + } + } } 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..0456b3f17 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 @@ -37,8 +37,8 @@ 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.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,7 +106,10 @@ public final class SparseDirectoryTrustedChecksumsSource extends FileTrustedChec @Inject public SparseDirectoryTrustedChecksumsSource( - ChecksumProcessor checksumProcessor, LocalPathComposer localPathComposer) { + RepositoryKeyFunctionFactory repositoryKeyFunctionFactory, + ChecksumProcessor checksumProcessor, + LocalPathComposer localPathComposer) { + super(repositoryKeyFunctionFactory); this.checksumProcessor = requireNonNull(checksumProcessor); this.localPathComposer = requireNonNull(localPathComposer); } @@ -132,10 +135,7 @@ protected Map doGetTrustedArtifactChecksums( if (Files.isDirectory(basedir)) { for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) { Path checksumPath = basedir.resolve(calculateArtifactPath( - originAware, - artifact, - RepositoryIdHelper.cachedIdToPathSegment(session).apply(artifactRepository), - checksumAlgorithmFactory)); + originAware, artifact, repositoryKey(session, artifactRepository), checksumAlgorithmFactory)); if (!Files.isRegularFile(checksumPath)) { LOGGER.debug( @@ -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 -> repositoryKey(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..f7f451546 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 @@ -46,8 +46,8 @@ 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.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.repository.RepositoryIdHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -142,9 +142,11 @@ public final class SummaryFileTrustedChecksumsSource extends FileTrustedChecksum @Inject public SummaryFileTrustedChecksumsSource( + RepositoryKeyFunctionFactory repoKeyFunctionFactory, LocalPathComposer localPathComposer, RepositorySystemLifecycle repositorySystemLifecycle, PathProcessor pathProcessor) { + super(repoKeyFunctionFactory); this.localPathComposer = requireNonNull(localPathComposer); this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle); this.pathProcessor = requireNonNull(pathProcessor); @@ -177,7 +179,7 @@ protected Map doGetTrustedArtifactChecksums( Path summaryFile = summaryFile( basedir, originAware, - RepositoryIdHelper.cachedIdToPathSegment(session).apply(artifactRepository), + repositoryKey(session, artifactRepository), checksumAlgorithmFactory.getFileExtension()); ConcurrentHashMap algorithmChecksums = checksums.computeIfAbsent(summaryFile, f -> loadProvidedChecksums(summaryFile)); @@ -199,7 +201,7 @@ protected Writer doGetTrustedArtifactChecksumsWriter(RepositorySystemSession ses checksums, getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, true), isOriginAware(session), - RepositoryIdHelper.cachedIdToPathSegment(session)); + r -> repositoryKey(session, r)); } /** @@ -263,17 +265,17 @@ private class SummaryFileWriter implements Writer { private final boolean originAware; - private final Function idToPathSegmentFunction; + private final Function repositoryKeyFunction; private SummaryFileWriter( ConcurrentHashMap> cache, Path basedir, boolean originAware, - Function idToPathSegmentFunction) { + Function repositoryKeyFunction) { this.cache = cache; this.basedir = basedir; this.originAware = originAware; - this.idToPathSegmentFunction = idToPathSegmentFunction; + this.repositoryKeyFunction = repositoryKeyFunction; } @Override @@ -287,7 +289,7 @@ public void addTrustedArtifactChecksums( Path summaryFile = summaryFile( basedir, originAware, - idToPathSegmentFunction.apply(artifactRepository), + repositoryKeyFunction.apply(artifactRepository), checksumAlgorithmFactory.getFileExtension()); String checksum = requireNonNull(trustedArtifactChecksums.get(checksumAlgorithmFactory.getName())); 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..32be14c79 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 @@ -48,9 +48,9 @@ import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter; import org.eclipse.aether.spi.io.PathProcessor; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; 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; @@ -157,7 +157,10 @@ public final class GroupIdRemoteRepositoryFilterSource extends RemoteRepositoryF @Inject public GroupIdRemoteRepositoryFilterSource( - RepositorySystemLifecycle repositorySystemLifecycle, PathProcessor pathProcessor) { + RepositoryKeyFunctionFactory repositoryKeyFunctionFactory, + RepositorySystemLifecycle repositorySystemLifecycle, + PathProcessor pathProcessor) { + super(repositoryKeyFunctionFactory); this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle); this.pathProcessor = requireNonNull(pathProcessor); } @@ -248,9 +251,7 @@ public void postProcess(RepositorySystemSession session, List ar private Path ruleFile(RepositorySystemSession session, RemoteRepository remoteRepository) { 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) - + GROUP_ID_FILE_SUFFIX)); + .resolve(GROUP_ID_FILE_PREFIX + repositoryKey(session, remoteRepository) + GROUP_ID_FILE_SUFFIX)); } private GroupTree cacheRules(RepositorySystemSession session, RemoteRepository remoteRepository) { diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSource.java index 9a4b2c301..8e77aab21 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 @@ -47,9 +47,9 @@ import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter; import org.eclipse.aether.spi.connector.layout.RepositoryLayout; import org.eclipse.aether.spi.connector.layout.RepositoryLayoutProvider; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; 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; @@ -197,9 +197,11 @@ public final class PrefixesRemoteRepositoryFilterSource extends RemoteRepository @Inject public PrefixesRemoteRepositoryFilterSource( + RepositoryKeyFunctionFactory repositoryKeyFunctionFactory, Supplier metadataResolver, Supplier remoteRepositoryManager, RepositoryLayoutProvider repositoryLayoutProvider) { + super(repositoryKeyFunctionFactory); this.metadataResolver = requireNonNull(metadataResolver); this.remoteRepositoryManager = requireNonNull(remoteRepositoryManager); this.repositoryLayoutProvider = requireNonNull(repositoryLayoutProvider); @@ -318,9 +320,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 + repositoryKey(session, remoteRepository) + PREFIXES_FILE_SUFFIX); if (Files.isReadable(filePath)) { return filePath; } else { diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java index 438c5eaa8..e97900d11 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java @@ -24,9 +24,11 @@ import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.internal.impl.checksum.FileTrustedChecksumsSourceSupport; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilter; import org.eclipse.aether.spi.connector.filter.RemoteRepositoryFilterSource; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.util.DirectoryUtils; import static java.util.Objects.requireNonNull; @@ -52,6 +54,27 @@ public abstract class RemoteRepositoryFilterSourceSupport implements RemoteRepos protected static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "remoteRepositoryFilter."; + /** + * Experimental: Configuration for "repository key" function. + * Note: repository key functions other than "nid" produce repository keys will be way different + * that those produced with previous versions or without this option enabled. Filter uses this key function to + * lay down and look up files to use in filtering. + * + * @since 2.0.14 + * @configurationSource {@link RepositorySystemSession#getConfigProperties()} + * @configurationType {@link java.lang.String} + * @configurationDefaultValue {@link #DEFAULT_REPOSITORY_KEY_FUNCTION} + */ + public static final String CONFIG_PROP_REPOSITORY_KEY_FUNCTION = CONFIG_PROPS_PREFIX + "repositoryKeyFunction"; + + public static final String DEFAULT_REPOSITORY_KEY_FUNCTION = "nid"; + + private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory; + + protected RemoteRepositoryFilterSourceSupport(RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) { + this.repositoryKeyFunctionFactory = requireNonNull(repositoryKeyFunctionFactory); + } + /** * Returns {@code true} if session configuration contains this name set to {@code true}. *

@@ -88,6 +111,21 @@ protected RemoteRepository normalizeRemoteRepository( return remoteRepository.toBareRemoteRepository(); } + /** + * Returns repository key to be used on file system layout. + * + * @since 2.0.14 + */ + protected String repositoryKey(RepositorySystemSession session, RemoteRepository repository) { + return repositoryKeyFunctionFactory + .repositoryKeyFunction( + FileTrustedChecksumsSourceSupport.class, + session, + DEFAULT_REPOSITORY_KEY_FUNCTION, + CONFIG_PROP_REPOSITORY_KEY_FUNCTION) + .apply(repository, null); + } + /** * Simple {@link RemoteRepositoryFilter.Result} immutable implementation. */ diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java index 0a47ac7ae..8f6c53452 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java @@ -56,7 +56,8 @@ public class DefaultLocalPathPrefixComposerFactoryTest { void defaultConfigNoSplitAllNulls() { DefaultRepositorySystemSession session = TestUtils.newSession(); - LocalPathPrefixComposerFactory factory = new DefaultLocalPathPrefixComposerFactory(); + LocalPathPrefixComposerFactory factory = + new DefaultLocalPathPrefixComposerFactory(new DefaultRepositoryKeyFunctionFactory()); LocalPathPrefixComposer composer = factory.createComposer(session); assertNotNull(composer); @@ -79,7 +80,8 @@ void splitEnabled() { DefaultRepositorySystemSession session = TestUtils.newSession(); session.setConfigProperty(DefaultLocalPathPrefixComposerFactory.CONFIG_PROP_SPLIT, Boolean.TRUE.toString()); - LocalPathPrefixComposerFactory factory = new DefaultLocalPathPrefixComposerFactory(); + LocalPathPrefixComposerFactory factory = + new DefaultLocalPathPrefixComposerFactory(new DefaultRepositoryKeyFunctionFactory()); LocalPathPrefixComposer composer = factory.createComposer(session); assertNotNull(composer); @@ -110,7 +112,8 @@ void saneConfig() { session.setConfigProperty( DefaultLocalPathPrefixComposerFactory.CONFIG_PROP_SPLIT_REMOTE_REPOSITORY, Boolean.TRUE.toString()); - LocalPathPrefixComposerFactory factory = new DefaultLocalPathPrefixComposerFactory(); + LocalPathPrefixComposerFactory factory = + new DefaultLocalPathPrefixComposerFactory(new DefaultRepositoryKeyFunctionFactory()); LocalPathPrefixComposer composer = factory.createComposer(session); assertNotNull(composer); @@ -175,7 +178,8 @@ void fullConfig() { session.setConfigProperty( DefaultLocalPathPrefixComposerFactory.CONFIG_PROP_SPLIT_REMOTE_REPOSITORY, Boolean.TRUE.toString()); - LocalPathPrefixComposerFactory factory = new DefaultLocalPathPrefixComposerFactory(); + LocalPathPrefixComposerFactory factory = + new DefaultLocalPathPrefixComposerFactory(new DefaultRepositoryKeyFunctionFactory()); LocalPathPrefixComposer composer = factory.createComposer(session); assertNotNull(composer); diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java index 20ec8462e..9b51a2db5 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java @@ -53,8 +53,10 @@ void setup() { session = TestUtils.newSession(); session.setChecksumPolicy(null); session.setUpdatePolicy(null); - manager = - new DefaultRemoteRepositoryManager(new StubUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider()); + manager = new DefaultRemoteRepositoryManager( + new StubUpdatePolicyAnalyzer(), + new DefaultChecksumPolicyProvider(), + new DefaultRepositoryKeyFunctionFactory()); } @AfterEach diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java index 08b75df3f..4fa1953d5 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java @@ -64,7 +64,9 @@ void init() { mock(LocalRepositoryProvider.class), new StubSyncContextFactory(), new DefaultRemoteRepositoryManager( - new DefaultUpdatePolicyAnalyzer(), new DefaultChecksumPolicyProvider()), + new DefaultUpdatePolicyAnalyzer(), + new DefaultChecksumPolicyProvider(), + new DefaultRepositoryKeyFunctionFactory()), new DefaultRepositorySystemLifecycle(), Collections.emptyMap(), new DefaultRepositorySystemValidator(Collections.emptyList())); 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..2e3468dc6 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,10 +108,11 @@ protected EnhancedLocalRepositoryManager getManager() { return new EnhancedLocalRepositoryManager( basedir.toPath(), new DefaultLocalPathComposer(), - ArtifactRepository::getId, + RepositoryIdHelper::simpleRepositoryKey, "_remote.repositories", trackingFileManager, - new DefaultLocalPathPrefixComposerFactory().createComposer(session)); + new DefaultLocalPathPrefixComposerFactory(new DefaultRepositoryKeyFunctionFactory()) + .createComposer(session)); } @AfterEach 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..538c7afe0 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,10 +34,11 @@ protected EnhancedLocalRepositoryManager getManager() { return new EnhancedLocalRepositoryManager( basedir.toPath(), new DefaultLocalPathComposer(), - ArtifactRepository::getId, + RepositoryIdHelper::simpleRepositoryKey, "_remote.repositories", trackingFileManager, - new DefaultLocalPathPrefixComposerFactory().createComposer(session)); + new DefaultLocalPathPrefixComposerFactory(new DefaultRepositoryKeyFunctionFactory()) + .createComposer(session)); } @Test 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-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java index 83e812a57..2053ee489 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java @@ -23,12 +23,15 @@ import org.eclipse.aether.internal.impl.DefaultChecksumProcessor; import org.eclipse.aether.internal.impl.DefaultLocalPathComposer; import org.eclipse.aether.internal.impl.DefaultPathProcessor; +import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory; public class SparseDirectoryTrustedChecksumsSourceTest extends FileTrustedChecksumsSourceTestSupport { @Override protected FileTrustedChecksumsSourceSupport prepareSubject(RepositorySystemLifecycle lifecycle) { return new SparseDirectoryTrustedChecksumsSource( - new DefaultChecksumProcessor(new DefaultPathProcessor()), new DefaultLocalPathComposer()); + new DefaultRepositoryKeyFunctionFactory(), + new DefaultChecksumProcessor(new DefaultPathProcessor()), + new DefaultLocalPathComposer()); } @Override diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java index 2646d783c..6d31e5da8 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java @@ -26,6 +26,7 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.impl.RepositorySystemLifecycle; import org.eclipse.aether.internal.impl.DefaultLocalPathComposer; +import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory; import org.eclipse.aether.internal.impl.DefaultRepositorySystemLifecycle; import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory; import org.eclipse.aether.repository.LocalRepository; @@ -44,7 +45,10 @@ public class SummaryFileTrustedChecksumsSourceTest extends FileTrustedChecksumsS @Override protected FileTrustedChecksumsSourceSupport prepareSubject(RepositorySystemLifecycle lifecycle) { return new SummaryFileTrustedChecksumsSource( - new DefaultLocalPathComposer(), lifecycle, new PathProcessorSupport()); + new DefaultRepositoryKeyFunctionFactory(), + new DefaultLocalPathComposer(), + lifecycle, + new PathProcessorSupport()); } @Override diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSourceTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSourceTest.java index 4a60622ad..6bcbdaaf9 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSourceTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/GroupIdRemoteRepositoryFilterSourceTest.java @@ -26,6 +26,7 @@ import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory; import org.eclipse.aether.internal.impl.DefaultRepositorySystemLifecycle; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactRequest; @@ -42,7 +43,9 @@ public class GroupIdRemoteRepositoryFilterSourceTest extends RemoteRepositoryFil protected GroupIdRemoteRepositoryFilterSource getRemoteRepositoryFilterSource( DefaultRepositorySystemSession session, RemoteRepository remoteRepository) { return groupIdRemoteRepositoryFilterSource = new GroupIdRemoteRepositoryFilterSource( - new DefaultRepositorySystemLifecycle(), new PathProcessorSupport()); + new DefaultRepositoryKeyFunctionFactory(), + new DefaultRepositorySystemLifecycle(), + new PathProcessorSupport()); } @Override diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java index 7d7c21782..5db48616f 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/filter/PrefixesRemoteRepositoryFilterSourceTest.java @@ -33,6 +33,7 @@ import org.eclipse.aether.impl.MetadataResolver; import org.eclipse.aether.impl.RemoteRepositoryManager; import org.eclipse.aether.internal.impl.DefaultArtifactPredicateFactory; +import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory; import org.eclipse.aether.internal.impl.DefaultRepositoryLayoutProvider; import org.eclipse.aether.internal.impl.Maven2RepositoryLayoutFactory; import org.eclipse.aether.repository.RemoteRepository; @@ -84,7 +85,10 @@ public RepositoryPolicy getPolicy( new Maven2RepositoryLayoutFactory( checksumsSelector(), new DefaultArtifactPredicateFactory(checksumsSelector())))); return new PrefixesRemoteRepositoryFilterSource( - () -> metadataResolver, () -> remoteRepositoryManager, layoutProvider); + new DefaultRepositoryKeyFunctionFactory(), + () -> metadataResolver, + () -> remoteRepositoryManager, + layoutProvider); } @Override diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/remoterepo/RepositoryKeyFunctionFactory.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/remoterepo/RepositoryKeyFunctionFactory.java new file mode 100644 index 000000000..d537a10f6 --- /dev/null +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/remoterepo/RepositoryKeyFunctionFactory.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.eclipse.aether.spi.remoterepo; + +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.repository.RepositoryKeyFunction; + +/** + * A factory to create {@link RepositoryKeyFunction} instances. + * + * @since 2.0.14 + */ +public interface RepositoryKeyFunctionFactory { + /** + * Returns system-wide repository key function. + * + * @param session The repository session, must not be {@code null}. + * @return The repository key function. + * @see #repositoryKeyFunction(Class, RepositorySystemSession, String, String) + * @see org.eclipse.aether.ConfigurationProperties#REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION + */ + RepositoryKeyFunction systemRepositoryKeyFunction(RepositorySystemSession session); + + /** + * Method that based on configuration returns the "repository key function". The returned function will be session + * cached if session is equipped with cache, otherwise it will be non cached. Method never returns {@code null}. + * Only the {@code configurationKey} parameter may be {@code null} in which case no configuration lookup happens + * but the {@code defaultValue} is directly used instead. + * + * @param owner The "owner" of key function (used to create cache-key), must not be {@code null}. + * @param session The repository session, must not be {@code null}. + * @param defaultValue The default value of repository key configuration, must not be {@code null}. + * @param configurationKey The configuration key to lookup configuration from, may be {@code null}, in which case + * no configuration lookup happens but the {@code defaultValue} is used to create the + * repository key function. + * @return The repository key function. + */ + RepositoryKeyFunction repositoryKeyFunction( + Class owner, RepositorySystemSession session, String defaultValue, String configurationKey); +} diff --git a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/remoterepo/package-info.java b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/remoterepo/package-info.java new file mode 100644 index 000000000..0a53f8235 --- /dev/null +++ b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/remoterepo/package-info.java @@ -0,0 +1,23 @@ +// CHECKSTYLE_OFF: RegexpHeader +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/** + * The contract for remote repository customizations. + */ +package org.eclipse.aether.spi.remoterepo; diff --git a/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java b/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java index c104ebc45..26abebd3e 100644 --- a/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java +++ b/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java @@ -72,6 +72,7 @@ import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager; import org.eclipse.aether.internal.impl.DefaultRepositoryConnectorProvider; import org.eclipse.aether.internal.impl.DefaultRepositoryEventDispatcher; +import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory; import org.eclipse.aether.internal.impl.DefaultRepositoryLayoutProvider; import org.eclipse.aether.internal.impl.DefaultRepositorySystem; import org.eclipse.aether.internal.impl.DefaultRepositorySystemLifecycle; @@ -138,6 +139,7 @@ import org.eclipse.aether.spi.io.ChecksumProcessor; import org.eclipse.aether.spi.io.PathProcessor; import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor; import org.eclipse.aether.spi.synccontext.SyncContextFactory; import org.eclipse.aether.spi.validator.ValidatorFactory; @@ -247,7 +249,7 @@ public final LocalPathPrefixComposerFactory getLocalPathPrefixComposerFactory() } protected LocalPathPrefixComposerFactory createLocalPathPrefixComposerFactory() { - return new DefaultLocalPathPrefixComposerFactory(); + return new DefaultLocalPathPrefixComposerFactory(getRepositoryKeyFunctionFactory()); } private RepositorySystemLifecycle repositorySystemLifecycle; @@ -321,6 +323,20 @@ protected UpdateCheckManager createUpdateCheckManager() { return new DefaultUpdateCheckManager(getTrackingFileManager(), getUpdatePolicyAnalyzer(), getPathProcessor()); } + private RepositoryKeyFunctionFactory repositoriesKeyFunctionFactory; + + public final RepositoryKeyFunctionFactory getRepositoryKeyFunctionFactory() { + checkClosed(); + if (repositoriesKeyFunctionFactory == null) { + repositoriesKeyFunctionFactory = createRepositoryKeyFunctionFactory(); + } + return repositoriesKeyFunctionFactory; + } + + protected RepositoryKeyFunctionFactory createRepositoryKeyFunctionFactory() { + return new DefaultRepositoryKeyFunctionFactory(); + } + private Map namedLockFactories; public final Map getNamedLockFactories() { @@ -484,13 +500,18 @@ public final LocalRepositoryProvider getLocalRepositoryProvider() { protected LocalRepositoryProvider createLocalRepositoryProvider() { LocalPathComposer localPathComposer = getLocalPathComposer(); + RepositoryKeyFunctionFactory repositoryKeyFunctionFactory = getRepositoryKeyFunctionFactory(); HashMap localRepositoryProviders = new HashMap<>(2); localRepositoryProviders.put( - SimpleLocalRepositoryManagerFactory.NAME, new SimpleLocalRepositoryManagerFactory(localPathComposer)); + SimpleLocalRepositoryManagerFactory.NAME, + new SimpleLocalRepositoryManagerFactory(localPathComposer, repositoryKeyFunctionFactory)); localRepositoryProviders.put( EnhancedLocalRepositoryManagerFactory.NAME, new EnhancedLocalRepositoryManagerFactory( - localPathComposer, getTrackingFileManager(), getLocalPathPrefixComposerFactory())); + localPathComposer, + getTrackingFileManager(), + getLocalPathPrefixComposerFactory(), + repositoryKeyFunctionFactory)); return new DefaultLocalRepositoryProvider(localRepositoryProviders); } @@ -505,7 +526,8 @@ public final RemoteRepositoryManager getRemoteRepositoryManager() { } protected RemoteRepositoryManager createRemoteRepositoryManager() { - return new DefaultRemoteRepositoryManager(getUpdatePolicyAnalyzer(), getChecksumPolicyProvider()); + return new DefaultRemoteRepositoryManager( + getUpdatePolicyAnalyzer(), getChecksumPolicyProvider(), getRepositoryKeyFunctionFactory()); } private Map remoteRepositoryFilterSources; @@ -522,11 +544,15 @@ protected Map createRemoteRepositoryFilter HashMap result = new HashMap<>(); result.put( GroupIdRemoteRepositoryFilterSource.NAME, - new GroupIdRemoteRepositoryFilterSource(getRepositorySystemLifecycle(), getPathProcessor())); + new GroupIdRemoteRepositoryFilterSource( + getRepositoryKeyFunctionFactory(), getRepositorySystemLifecycle(), getPathProcessor())); result.put( PrefixesRemoteRepositoryFilterSource.NAME, new PrefixesRemoteRepositoryFilterSource( - this::getMetadataResolver, this::getRemoteRepositoryManager, getRepositoryLayoutProvider())); + getRepositoryKeyFunctionFactory(), + this::getMetadataResolver, + this::getRemoteRepositoryManager, + getRepositoryLayoutProvider())); return result; } @@ -586,11 +612,15 @@ protected Map createTrustedChecksumsSources() { HashMap result = new HashMap<>(); result.put( SparseDirectoryTrustedChecksumsSource.NAME, - new SparseDirectoryTrustedChecksumsSource(getChecksumProcessor(), getLocalPathComposer())); + new SparseDirectoryTrustedChecksumsSource( + getRepositoryKeyFunctionFactory(), getChecksumProcessor(), getLocalPathComposer())); result.put( SummaryFileTrustedChecksumsSource.NAME, new SummaryFileTrustedChecksumsSource( - getLocalPathComposer(), getRepositorySystemLifecycle(), getPathProcessor())); + getRepositoryKeyFunctionFactory(), + getLocalPathComposer(), + getRepositorySystemLifecycle(), + getPathProcessor())); return result; } diff --git a/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java b/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java index 0fc19c0e9..870b6b1c8 100644 --- a/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java +++ b/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java @@ -76,6 +76,7 @@ import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager; import org.eclipse.aether.internal.impl.DefaultRepositoryConnectorProvider; import org.eclipse.aether.internal.impl.DefaultRepositoryEventDispatcher; +import org.eclipse.aether.internal.impl.DefaultRepositoryKeyFunctionFactory; import org.eclipse.aether.internal.impl.DefaultRepositoryLayoutProvider; import org.eclipse.aether.internal.impl.DefaultRepositorySystem; import org.eclipse.aether.internal.impl.DefaultRepositorySystemLifecycle; @@ -142,6 +143,7 @@ import org.eclipse.aether.spi.io.ChecksumProcessor; import org.eclipse.aether.spi.io.PathProcessor; import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory; +import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor; import org.eclipse.aether.spi.synccontext.SyncContextFactory; import org.eclipse.aether.spi.validator.ValidatorFactory; @@ -251,7 +253,7 @@ public final LocalPathPrefixComposerFactory getLocalPathPrefixComposerFactory() } protected LocalPathPrefixComposerFactory createLocalPathPrefixComposerFactory() { - return new DefaultLocalPathPrefixComposerFactory(); + return new DefaultLocalPathPrefixComposerFactory(getRepositoryKeyFunctionFactory()); } private RepositorySystemLifecycle repositorySystemLifecycle; @@ -325,6 +327,20 @@ protected UpdateCheckManager createUpdateCheckManager() { return new DefaultUpdateCheckManager(getTrackingFileManager(), getUpdatePolicyAnalyzer(), getPathProcessor()); } + private RepositoryKeyFunctionFactory repositoriesKeyFunctionFactory; + + public final RepositoryKeyFunctionFactory getRepositoryKeyFunctionFactory() { + checkClosed(); + if (repositoriesKeyFunctionFactory == null) { + repositoriesKeyFunctionFactory = createRepositoryKeyFunctionFactory(); + } + return repositoriesKeyFunctionFactory; + } + + protected RepositoryKeyFunctionFactory createRepositoryKeyFunctionFactory() { + return new DefaultRepositoryKeyFunctionFactory(); + } + private Map namedLockFactories; public final Map getNamedLockFactories() { @@ -488,13 +504,18 @@ public final LocalRepositoryProvider getLocalRepositoryProvider() { protected LocalRepositoryProvider createLocalRepositoryProvider() { LocalPathComposer localPathComposer = getLocalPathComposer(); + RepositoryKeyFunctionFactory repositoryKeyFunctionFactory = getRepositoryKeyFunctionFactory(); HashMap localRepositoryProviders = new HashMap<>(2); localRepositoryProviders.put( - SimpleLocalRepositoryManagerFactory.NAME, new SimpleLocalRepositoryManagerFactory(localPathComposer)); + SimpleLocalRepositoryManagerFactory.NAME, + new SimpleLocalRepositoryManagerFactory(localPathComposer, repositoryKeyFunctionFactory)); localRepositoryProviders.put( EnhancedLocalRepositoryManagerFactory.NAME, new EnhancedLocalRepositoryManagerFactory( - localPathComposer, getTrackingFileManager(), getLocalPathPrefixComposerFactory())); + localPathComposer, + getTrackingFileManager(), + getLocalPathPrefixComposerFactory(), + repositoryKeyFunctionFactory)); return new DefaultLocalRepositoryProvider(localRepositoryProviders); } @@ -509,7 +530,8 @@ public final RemoteRepositoryManager getRemoteRepositoryManager() { } protected RemoteRepositoryManager createRemoteRepositoryManager() { - return new DefaultRemoteRepositoryManager(getUpdatePolicyAnalyzer(), getChecksumPolicyProvider()); + return new DefaultRemoteRepositoryManager( + getUpdatePolicyAnalyzer(), getChecksumPolicyProvider(), getRepositoryKeyFunctionFactory()); } private Map remoteRepositoryFilterSources; @@ -526,11 +548,15 @@ protected Map createRemoteRepositoryFilter HashMap result = new HashMap<>(); result.put( GroupIdRemoteRepositoryFilterSource.NAME, - new GroupIdRemoteRepositoryFilterSource(getRepositorySystemLifecycle(), getPathProcessor())); + new GroupIdRemoteRepositoryFilterSource( + getRepositoryKeyFunctionFactory(), getRepositorySystemLifecycle(), getPathProcessor())); result.put( PrefixesRemoteRepositoryFilterSource.NAME, new PrefixesRemoteRepositoryFilterSource( - this::getMetadataResolver, this::getRemoteRepositoryManager, getRepositoryLayoutProvider())); + getRepositoryKeyFunctionFactory(), + this::getMetadataResolver, + this::getRemoteRepositoryManager, + getRepositoryLayoutProvider())); return result; } @@ -590,11 +616,15 @@ protected Map createTrustedChecksumsSources() { HashMap result = new HashMap<>(); result.put( SparseDirectoryTrustedChecksumsSource.NAME, - new SparseDirectoryTrustedChecksumsSource(getChecksumProcessor(), getLocalPathComposer())); + new SparseDirectoryTrustedChecksumsSource( + getRepositoryKeyFunctionFactory(), getChecksumProcessor(), getLocalPathComposer())); result.put( SummaryFileTrustedChecksumsSource.NAME, new SummaryFileTrustedChecksumsSource( - getLocalPathComposer(), getRepositorySystemLifecycle(), getPathProcessor())); + getRepositoryKeyFunctionFactory(), + getLocalPathComposer(), + getRepositorySystemLifecycle(), + getPathProcessor())); return result; } 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 5c8bbbbcd..6cacbb707 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,27 +18,26 @@ */ package org.eclipse.aether.util.repository; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; +import java.util.ArrayList; +import java.util.Comparator; import java.util.Locale; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.function.Predicate; +import java.util.SortedSet; +import java.util.TreeSet; -import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryKeyFunction; 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 @@ -46,103 +45,167 @@ 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(); + /** + * Supported {@code repositoryKey} types. + * + * @since 2.0.14 + */ + public enum RepositoryKeyType { + /** + * The "simple" repository key, was default in Maven 3. + */ + SIMPLE, + /** + * Crafts repository key using normalized {@link RemoteRepository#getId()}. + */ + NID, + /** + * Crafts repository key using hashed {@link RemoteRepository#getUrl()}. + */ + HURL, + /** + * Crafts unique repository key using normalized {@link RemoteRepository#getId()} and hashed {@link RemoteRepository#getUrl()}. + */ + NID_HURL, + /** + * Crafts normalized unique repository key using {@link RemoteRepository#getId()} and all the remaining properties of + * {@link RemoteRepository} ignoring actual list of mirrors, if any (but mirrors are split). + */ + NGURK, + /** + * Crafts unique repository key using {@link RemoteRepository#getId()} and all the remaining properties of + * {@link RemoteRepository}. + */ + GURK + } /** - * 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. - *

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

- * Important:Do not use this method, or at least do consider when do you want to use it, as it - * totally disconnects repositories used in session. This method may be used under some special circumstances - * (ie reporting), but must not be used within Resolver (and Maven) session for "usual" resolution and - * deployment use cases. + * Selector method for {@link RepositoryKeyFunction} based on string representation of {@link RepositoryKeyType} + * enum. */ - public static String remoteRepositoryUniqueId(RemoteRepository repository) { - if (CENTRAL_DIRECT_ONLY.test(repository)) { - return CENTRAL_REPOSITORY_ID; - } 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("); - for (RemoteRepository mirroredRepo : repository.getMirroredRepositories()) { - buffer.append(remoteRepositoryUniqueId(mirroredRepo)); - } - buffer.append(")"); + public static RepositoryKeyFunction getRepositoryKeyFunction(String keyTypeString) { + RepositoryKeyType keyType = RepositoryKeyType.valueOf(keyTypeString.toUpperCase(Locale.ENGLISH)); + switch (keyType) { + case SIMPLE: + return RepositoryIdHelper::simpleRepositoryKey; + case NID: + return RepositoryIdHelper::nidRepositoryKey; + case HURL: + return RepositoryIdHelper::hurlRepositoryKey; + case NID_HURL: + return RepositoryIdHelper::nidAndHurlRepositoryKey; + case NGURK: + return RepositoryIdHelper::normalizedGloballyUniqueRepositoryKey; + case GURK: + return RepositoryIdHelper::globallyUniqueRepositoryKey; + default: + throw new IllegalArgumentException("Unknown repository key type: " + keyType.name()); + } + } + + /** + * 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 and context. + *

+ * This was the default {@code repositoryKey} method in Maven 3. Is exposed (others key methods are private) as + * it is directly used by "simple" LRM. + * + * @since 2.0.14 + **/ + public static String simpleRepositoryKey(RemoteRepository repository, String context) { + 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()); } - if (repository.isBlocked()) { - buffer.append(", blocked"); + StringDigestUtil sha1 = StringDigestUtil.sha1(); + sha1.update(context); + for (String subKey : subKeys) { + sha1.update(subKey); } - buffer.append(")"); - return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(buffer.toString()); + buffer.append(sha1.digest()); + return buffer.toString(); + } else { + return idToPathSegment(repository); } } /** - * 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)); + * The ID {@code repositoryKey} function that uses only the {@link RemoteRepository#getId()} value to derive a key. + * + * @since 2.0.14 + **/ + private static String nidRepositoryKey(RemoteRepository repository, String context) { + String seed = null; + if (repository.isRepositoryManager() && context != null && !context.isEmpty()) { + seed += context; + } + return idToPathSegment(repository) + (seed == null ? "" : "-" + StringDigestUtil.sha1(seed)); } /** - * 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 + * The URL {@code repositoryKey} function that uses only the {@link RemoteRepository#getUrl()} hash to derive a key. + * + * @since 2.0.14 + **/ + private static String hurlRepositoryKey(RemoteRepository repository, String context) { + String seed = null; + if (repository.isRepositoryManager() && context != null && !context.isEmpty()) { + seed += context; + } + return StringDigestUtil.sha1(repository.getUrl()) + (seed == null ? "" : "-" + StringDigestUtil.sha1(seed)); + } + + /** + * The ID and URL {@code repositoryKey} function. This method creates unique identifier based on ID and URL + * of the remote repository. + * + * @since 2.0.14 + **/ + private static String nidAndHurlRepositoryKey(RemoteRepository repository, String context) { + String seed = repository.getUrl(); + if (repository.isRepositoryManager() && context != null && !context.isEmpty()) { + seed += context; + } + return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(seed); + } + + /** + * Normalized globally unique {@code repositoryKey} function. This method creates unique identifier based on ID and current + * configuration of the remote repository ignoring mirrors (it records the fact repository is a mirror, but ignores + * mirrored repositories). If {@link RemoteRepository#isRepositoryManager()} returns {@code true}, the passed in + * {@code context} string is factored in as well. + * + * @since 2.0.14 + **/ + private static String normalizedGloballyUniqueRepositoryKey(RemoteRepository repository, String context) { + String seed = remoteRepositoryDescription(repository, false); + if (repository.isRepositoryManager() && context != null && !context.isEmpty()) { + seed += context; } + return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(seed); + } + + /** + * 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. + *

+ * 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. + * + * @since 2.0.14 + **/ + private static String globallyUniqueRepositoryKey(RemoteRepository repository, String context) { + String seed = remoteRepositoryDescription(repository, true); + if (repository.isRepositoryManager() && context != null && !context.isEmpty()) { + seed += context; + } + return idToPathSegment(repository) + "-" + StringDigestUtil.sha1(seed); } /** @@ -150,11 +213,6 @@ private static Function cachedIdToPathSegmentFunctio * 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) { @@ -163,4 +221,53 @@ private static String idToPathSegment(ArtifactRepository repository) { return repository.getId(); } } + + /** + * Creates unique string for given {@link RemoteRepository}. Ignores following properties: + *

    + *
  • {@link RemoteRepository#getAuthentication()}
  • + *
  • {@link RemoteRepository#getProxy()}
  • + *
  • {@link RemoteRepository#getIntent()}
  • + *
+ */ + private static String remoteRepositoryDescription(RemoteRepository repository, boolean mirrorDetails) { + 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()) { + if (mirrorDetails) { + // sort them to make it stable ordering + ArrayList mirroredRepositories = + new ArrayList<>(repository.getMirroredRepositories()); + mirroredRepositories.sort(Comparator.comparing(RemoteRepository::getId)); + buffer.append(", mirrorOf("); + for (RemoteRepository mirroredRepo : mirroredRepositories) { + buffer.append(remoteRepositoryDescription(mirroredRepo, true)); + } + buffer.append(")"); + } else { + buffer.append(", isMirror"); + } + } + if (repository.isBlocked()) { + buffer.append(", blocked"); + } + buffer.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..74f5b73b5 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 @@ -18,59 +18,87 @@ */ package org.eclipse.aether.util.repository; -import java.util.function.Function; +import java.util.Collections; -import org.eclipse.aether.DefaultRepositoryCache; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.repository.ArtifactRepository; import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryKeyFunction; +import org.eclipse.aether.repository.RepositoryPolicy; import org.junit.jupiter.api.Test; 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() { - DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(s -> false); - session.setCache(new DefaultRepositoryCache()); // session has 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); - assertSame(goodFixedId, safeId.apply(good)); + private final RemoteRepository central = new RemoteRepository.Builder( + "central", "default", "https://repo.maven.apache.org/maven2/") + .setSnapshotPolicy(new RepositoryPolicy(false, null, null)) + .build(); + private final RemoteRepository central_legacy = new RemoteRepository.Builder( + "central", "default", "https://repo1.maven.org/maven2/") + .setSnapshotPolicy(new RepositoryPolicy(false, null, null)) + .build(); + private final RemoteRepository central_trivial = + new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build(); + private final RemoteRepository central_mirror = new RemoteRepository.Builder( + "my-mirror", "default", "https://mymrm.com/maven/") + .setSnapshotPolicy(new RepositoryPolicy(false, null, null)) + .setMirroredRepositories(Collections.singletonList(central)) + .build(); + private final RemoteRepository asf_snapshots = new RemoteRepository.Builder( + "apache-snapshots", "default", "https://repository.apache.org/content/repositories/snapshots/") + .setReleasePolicy(new RepositoryPolicy(false, null, null)) + .build(); + private final RemoteRepository file_unfriendly = new RemoteRepository.Builder( + "apache/snapshots", "default", "https://repository.apache.org/content/repositories/snapshots/") + .setReleasePolicy(new RepositoryPolicy(false, null, null)) + .build(); - String badId = bad.getId(); - String badFixedId = safeId.apply(bad); - assertNotEquals(badId, badFixedId); - assertEquals("bad-SLASH-id", badFixedId); - assertSame(badFixedId, safeId.apply(bad)); + @Test + void simple() { + RepositoryKeyFunction func = + RepositoryIdHelper.getRepositoryKeyFunction(RepositoryIdHelper.RepositoryKeyType.SIMPLE.name()); + assertEquals("central", func.apply(central, null)); + assertEquals("central", func.apply(central_legacy, null)); + assertEquals("central", func.apply(central_trivial, null)); + assertEquals("my-mirror", func.apply(central_mirror, null)); + assertEquals("apache-snapshots", func.apply(asf_snapshots, null)); + assertEquals("apache-SLASH-snapshots", func.apply(file_unfriendly, null)); } @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(); + void nid() { + RepositoryKeyFunction func = + RepositoryIdHelper.getRepositoryKeyFunction(RepositoryIdHelper.RepositoryKeyType.NID.name()); + assertEquals("central", func.apply(central, null)); + assertEquals("central", func.apply(central_legacy, null)); + assertEquals("central", func.apply(central_trivial, null)); + assertEquals("my-mirror", func.apply(central_mirror, null)); + assertEquals("apache-snapshots", func.apply(asf_snapshots, null)); + assertEquals("apache-SLASH-snapshots", func.apply(file_unfriendly, null)); + } - String goodId = good.getId(); - String goodFixedId = safeId.apply(good); - assertEquals(goodId, goodFixedId); - assertNotSame(goodFixedId, safeId.apply(good)); + @Test + void nidHurl() { + RepositoryKeyFunction func = + RepositoryIdHelper.getRepositoryKeyFunction(RepositoryIdHelper.RepositoryKeyType.NID_HURL.name()); + assertEquals("central-0aeeb43004cebeccad6fdf0fec27084167d5880a", func.apply(central, null)); + assertEquals("central-a27bb55260d64d6035671716555d10644054c89d", func.apply(central_legacy, null)); + assertEquals("central-a27bb55260d64d6035671716555d10644054c89d", func.apply(central_trivial, null)); + assertEquals("my-mirror-eb106d0adc4a56b55067f069a2fed5526fd6cb18", func.apply(central_mirror, null)); + assertEquals("apache-snapshots-5c4f89479e3c71fb3c2fbc6213fb00f6371fbb96", func.apply(asf_snapshots, null)); + assertEquals( + "apache-SLASH-snapshots-5c4f89479e3c71fb3c2fbc6213fb00f6371fbb96", func.apply(file_unfriendly, null)); + } - String badId = bad.getId(); - String badFixedId = safeId.apply(bad); - assertNotEquals(badId, badFixedId); - assertEquals("bad-SLASH-id", badFixedId); - assertNotSame(badFixedId, safeId.apply(bad)); + @Test + void ngurk() { + RepositoryKeyFunction func = + RepositoryIdHelper.getRepositoryKeyFunction(RepositoryIdHelper.RepositoryKeyType.NGURK.name()); + assertEquals("central-ff5deec948d038ceb880e13e9f61455903b0d0a6", func.apply(central, null)); + assertEquals("central-ffb5c2a34e47c429571fc29752730e9ce6e44d79", func.apply(central_legacy, null)); + assertEquals("central-acc6c84ca8674036eda6708502b5f02fb09a9731", func.apply(central_trivial, null)); + assertEquals("my-mirror-256631324003f5718aca1e80db8377c7f9ecd852", func.apply(central_mirror, null)); + assertEquals("apache-snapshots-62375dea6c3c8bebdbae5cca79a4f5ad2eaebf34", func.apply(asf_snapshots, null)); + assertEquals( + "apache-SLASH-snapshots-2e126ec79795c077a3c42dc536fa28c13c3bdb0d", func.apply(file_unfriendly, null)); } } diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 4735a16ed..295203c09 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -107,6 +107,7 @@ To modify this file, edit the template and regenerate. | `"aether.remoteRepositoryFilter.prefixes.skipped"` | `Boolean` | Configuration to skip the Prefixes filter for given request. This configuration is evaluated and if true the prefixes remote filter will not kick in. Main use case is by filter itself, to prevent recursion during discovery of remote prefixes file, but this also allows other components to control prefix filter discovery, while leaving configuration like #CONFIG_PROP_ENABLED still show the "real state". | `false` | 2.0.14 | Yes | Session Configuration | | `"aether.remoteRepositoryFilter.prefixes.useMirroredRepositories"` | `Boolean` | Configuration to allow Prefixes filter to auto-discover prefixes from mirrored repositories as well. For this to work Maven should be aware that given remote repository is mirror and is usually backed by MRM. Given multiple MRM implementations messes up prefixes file, is better to just skip these. In other case, one may use #CONFIG_PROP_ENABLED with repository ID suffix. | `false` | 2.0.14 | Yes | Session Configuration | | `"aether.remoteRepositoryFilter.prefixes.useRepositoryManagers"` | `Boolean` | Configuration to allow Prefixes filter to auto-discover prefixes from repository managers as well. For this to work Maven should be aware that given remote repository is backed by repository manager. Given multiple MRM implementations messes up prefixes file, is better to just skip these. In other case, one may use #CONFIG_PROP_ENABLED with repository ID suffix. Note: as of today, nothing sets this on remote repositories, but is added for future. | `false` | 2.0.14 | Yes | Session Configuration | +| `"aether.remoteRepositoryFilter.repositoryKeyFunction"` | `String` | Experimental: Configuration for "repository key" function. Note: repository key functions other than "nid" produce repository keys will be way different that those produced with previous versions or without this option enabled. Filter uses this key function to lay down and look up files to use in filtering. | `"nid"` | 2.0.14 | No | Session Configuration | | `"aether.snapshotFilter"` | `Boolean` | The key in the repository session's RepositorySystemSession#getConfigProperties() configurationproperties used to store a Boolean flag whether this filter should be forced to ban snapshots. By default, snapshots are only filtered if the root artifact is not a snapshot. | `false` | | No | Session Configuration | | `"aether.syncContext.named.basedir.locksDir"` | `String` | The location of the directory toi use for locks. If relative path, it is resolved from the local repository root. | `".locks"` | 1.9.0 | No | Session Configuration | | `"aether.syncContext.named.discriminating.discriminator"` | `String` | Configuration property to pass in discriminator, if needed. If not present, it is auto-calculated. | - | 1.7.0 | No | Session Configuration | @@ -121,6 +122,7 @@ To modify this file, edit the template and regenerate. | `"aether.syncContext.named.time"` | `Long` | The maximum of time amount to be blocked to obtain lock. | `900l` | 1.7.0 | No | Session Configuration | | `"aether.syncContext.named.time.unit"` | `String` | The unit of maximum time amount to be blocked to obtain lock. Use TimeUnit enum names. | `"SECONDS"` | 1.7.0 | No | Session Configuration | | `"aether.system.dependencyVisitor"` | `String` | A flag indicating which visitor should be used to "flatten" the dependency graph into list. In Maven 4 the default is new "levelOrder", while Maven 3 used "preOrder". This property accepts values "preOrder", "postOrder" and "levelOrder". | `"levelOrder"` | 2.0.0 | No | Session Configuration | +| `"aether.system.repositoryKeyFunction"` | `String` | Experimental: Configuration for system-wide "repository key" function. Accepted and recommended values: "nid" (default), "nid_hurl" and "ngurk", while "simple" is Maven 3 legacy, technically equivalent to "nid". For complete description see enum org.eclipse.aether.util.repository.RepositoryIdHelper.RepositoryKeyType in utils. Warning: repository key function affects Resolver fundamentally and may have unexpected results! Only change this if you know what you are doing! | `"nid"` | 2.0.14 | No | Session Configuration | | `"aether.transport.apache.followRedirects"` | `Boolean` | If enabled, Apache HttpClient will follow HTTP redirects. | `true` | 2.0.2 | Yes | Session Configuration | | `"aether.transport.apache.https.cipherSuites"` | `String` | Comma-separated list of Cipher Suites which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | | `"aether.transport.apache.https.protocols"` | `String` | Comma-separated list of Protocols which are enabled for HTTPS connections. | - | 2.0.0 | No | Session Configuration | @@ -157,6 +159,7 @@ To modify this file, edit the template and regenerate. | `"aether.transport.wagon.perms.dirMode"` | `String` | Octal numerical notation of permissions to set for newly created directories. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | | `"aether.transport.wagon.perms.fileMode"` | `String` | Octal numerical notation of permissions to set for newly created files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | | `"aether.transport.wagon.perms.group"` | `String` | Group which should own newly created directories/files. Only considered by certain Wagon providers. | - | | Yes | Session Configuration | +| `"aether.trustedChecksumsSource.repositoryKeyFunction"` | `String` | Experimental: Configuration for "repository key" function. Note: repository key functions other than "nid" produce repository keys will be way different that those produced with previous versions or without this option enabled. Checksum source uses this key function to lay down and look up files to use in sources. | `"nid"` | 2.0.14 | No | Session Configuration | | `"aether.trustedChecksumsSource.sparseDirectory"` | `Boolean` | Is checksum source enabled? | `false` | 1.9.0 | No | Session Configuration | | `"aether.trustedChecksumsSource.sparseDirectory.basedir"` | `String` | The basedir where checksums are. If relative, is resolved from local repository root. | `".checksums"` | 1.9.0 | No | Session Configuration | | `"aether.trustedChecksumsSource.sparseDirectory.originAware"` | `Boolean` | Is source origin aware? | `true` | 1.9.0 | No | Session Configuration | diff --git a/src/site/markdown/repository-key-function.md b/src/site/markdown/repository-key-function.md new file mode 100644 index 000000000..250df5e28 --- /dev/null +++ b/src/site/markdown/repository-key-function.md @@ -0,0 +1,123 @@ +# Repository Key Function + + +One long outstanding issue in Maven (across all versions) was how to identify +remote repositories (this problem mostly tackles them, as local, workspace +and other repositories are usually "singletons" and have fixed IDs). + +Existing Maven versions mostly limited themselves to `RemoteRepository#getId()` +method to "key" repositories, but this strategy many times proves suboptimal. + +Known issues that Maven users cannot fight against: +* different IDs for same URLs, examples (from Central) are `apache-snapshots` (plural), `apache-snapshot` (singular) + or `apache.snapshot` (dot vs dash) defined repositories, that all point to same ASF snapshot repository. +* same IDs for different URLs (two totally disconnected project may define repository `project-releases` in their POM, + while in fact those two repositories are not related at all) +* repository IDs that are [not file-friendly](https://github.com/apache/maven-resolver/issues/1564). Usually this should + be impossible, as Maven validates and forbids these characters in ID field, but in some cases + (ancient or generated POMs) this may happen. + +Remote repositories that user cannot "fix", usually enter the build via those POMs that are not authored by user +themselves, so project POM and parent POMs can be safely excluded. In turn, these may come from POMs that are +being pulled in as third-party plugin or dependency POMs. + +While we don't find the first issue deal-breaker (and we did not provide yet a function for fixing it), the latter two +may produce various problems with local repository, split local repository and so on, causing a total mix-up of expected +layout, or even wrongly grouped artifacts. + +For those eager to fully control used repositories, Maven 3.9.x line added the `-itr`/`--ignore-transitive-repositories` +CLI option, but while this solves the problem, it does it by fully delegating the work onto the user itself, to define +all the needed remote repositories (for dependencies but also for plugins) in project POM that build requires. +In certain cases this option is the recommended way, but many times it proves too burdensome. + +Hence, Maven Resolver 2.x introduces notion of "repository key function", which is a function that creates +Remote Repository "key", with following properties: +* is derived from and can be used to identify `RemoteRepository` +* produced keys are "file system friendly" as well +* is configurable (see below) + +Latest Resolver uses repository key at these places (and these must be aligned; must use same function): +* `EnhancedLocalRepositoryManager`, the default LRM, where artifact availability is being calculated +* `LocalPathPrefixComposer`, in case of "split local repository" to calculate prefix/suffix elements based on artifact originating repository (if enabled) +* `RemoteRepositoryManager` that consolidates existing and newly discovered repositories (by eliminating them or merging mirrors, as needed) + +In these cases, the repository key function affects how Resolver (and hence, Maven) works _fundamentally_, what +`RemoteRepository` it considers "same" or "different". Which artifacts are considered as coming from "same origin" +or "different origin" (i.e. split local repository). + +Furthermore, repository key function (possibly different one) is used in two components to map remote repository configuration to file paths: +* Trusted Checksums Source +* Remote Repository Filter + +In these cases, the repository key function only role is to provide "file system friendly" path segments based on +`RemoteRepository` instances. + +**Important implication:** When Resolver/Maven is reconfigured to use alternative repository key function, it is +worthwhile to start with new, empty local repository (as keys are used in LRM maintained metadata). + +## Implemented Repository Key Functions + +The function is configurable, while the default function remains Maven 3.x compatible. The existing functions are: +* `simple` (Maven 3 default; technically equivalent to `nid`) +* `nid` (default) +* `nid_hurl` +* `ngurk` + +These below are recommended only for some special cases: +* `hurl` +* `gurk` + +## Recommended New Repository Key Functions + +### `nid` + +This key still relies solely on `RemoteRepository#getId()` but applies transformation to returned value to make it +"file system path segment friendly". Is usable in the simplest use cases, and behaves as Maven 3 did. +Technically is equivalent to legacy `simple` repository key function. + +### `nid_hurl` + +This key relies on `RemoteRepository#getId()` and `RemoteRepository#getUrl()`, and forms a key based on these two. +This means if you have same-ID repository pointing to two different URLs, they will be considered different. Still, +on disk the produced key string is user-friendly, as ID remains readable. + +### `ngurk` + +This key relies on **all properties** (details below) of `RemoteRepository`, but is "normalized" in a way that only the +fact that a `RemoteRepository` is a mirror (or not) is recorded, while the list of the mirrored repositories does not +affect key production. This also means that if you have two "similar" `RemoteRepository`, with same ID, same URL, but +one has snapshots enabled, the other snapshots disabled, they will be considered different. + +This function leaves out following `RemoteRepository` properties: `Authorization`, `Proxy`, `Intent`, `Mirrors` +(but checks is list empty or not) and update policies for releases and snapshots. + +## Special Repository Key Functions + +These functions are **not recommended for everyday use**, but may prove useful in some cases. + +### `hurl` + +This key relies solely on `RemoteRepository#getUrl()`. +This means that repository URL becomes what repository ID was for equality check. Note: this function does not perform +any kind of URL "normalization", URL is used as-is. The problem with this function is that it will produce +"human unfriendly" repository key that is fully disconnected and hard to trace back to origin repository. + +### `gurk` + +Similar to `ngurk` but does not normalize mirrors. As a consequence, and due dynamism of mirrors, the key of same +remote repository (for example `external:*`) **may change during the build**. diff --git a/src/site/site.xml b/src/site/site.xml index 91e5e983a..677ec4ff1 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -32,6 +32,7 @@ under the License. +