From d406d228d29140bdde94e45bfa21d15b67978aa7 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 16:57:10 +0100 Subject: [PATCH 01/13] [MRESOLVER-302] Introduce onSessionClose And make it use in new HTTP/2 transports. --- https://issues.apache.org/jira/browse/MRESOLVER-302 --- ...ractForwardingRepositorySystemSession.java | 5 + .../DefaultRepositorySystemSession.java | 24 +- .../org/eclipse/aether/RepositorySystem.java | 32 +- .../aether/RepositorySystemSession.java | 360 +++++++++++ .../impl/RepositorySystemLifecycle.java | 31 + .../impl/DefaultRepositorySystem.java | 23 +- .../DefaultRepositorySystemLifecycle.java | 58 ++ ...faultCloseableRepositorySystemSession.java | 328 ++++++++++ .../impl/session/DefaultSessionBuilder.java | 566 ++++++++++++++++++ .../supplier/RepositorySystemSupplier.java | 2 +- .../maven-resolver-transport-jdk-11/pom.xml | 4 + .../transport/jdk/JdkHttpTransporter.java | 13 +- .../maven-resolver-transport-jdk-8/pom.xml | 4 + .../transport/jdk/JdkTransporterFactory.java | 5 + .../maven-resolver-transport-jdk/pom.xml | 4 + maven-resolver-transport-jetty/pom.xml | 4 + .../transport/jetty/JettyTransporter.java | 14 + 17 files changed, 1467 insertions(+), 10 deletions(-) create mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java create mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java index 5e3a60d4f..9b7652f61 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java @@ -190,4 +190,9 @@ public SessionData getData() { public RepositoryCache getCache() { return getSession().getCache(); } + + @Override + public boolean addOnSessionEndedHandler(Runnable handler) { + return getSession().addOnSessionEndedHandler(handler); + } } diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java index 3f46af54f..5ad90103c 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/DefaultRepositorySystemSession.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import org.eclipse.aether.artifact.ArtifactType; import org.eclipse.aether.artifact.ArtifactTypeRegistry; @@ -51,10 +52,9 @@ * Note: This class is not thread-safe. It is assumed that the mutators get only called during an * initialization phase and that the session itself is not changed once initialized and being used by the repository * system. It is recommended to call {@link #setReadOnly()} once the session has been fully initialized to prevent - * accidental manipulation of it afterwards. + * accidental manipulation of it afterward. */ public final class DefaultRepositorySystemSession implements RepositorySystemSession { - private boolean readOnly; private boolean offline; @@ -113,11 +113,18 @@ public final class DefaultRepositorySystemSession implements RepositorySystemSes private RepositoryCache cache; + private final Function onSessionEndedRegistrar; + /** * Creates an uninitialized session. Note: The new session is not ready to use, as a bare minimum, * {@link #setLocalRepositoryManager(LocalRepositoryManager)} needs to be called but usually other settings also * need to be customized to achieve meaningful behavior. + * + * @deprecated This way of creating session should be avoided, is in place just to offer backward binary + * compatibility with Resolver 1.x using code, but offers reduced functionality. + * Use {@link RepositorySystem#createSessionBuilder()} instead. */ + @Deprecated public DefaultRepositorySystemSession() { systemProperties = new HashMap<>(); systemPropertiesView = Collections.unmodifiableMap(systemProperties); @@ -130,6 +137,7 @@ public DefaultRepositorySystemSession() { authenticationSelector = NullAuthenticationSelector.INSTANCE; artifactTypeRegistry = NullArtifactTypeRegistry.INSTANCE; data = new DefaultSessionData(); + onSessionEndedRegistrar = h -> false; } /** @@ -168,6 +176,7 @@ public DefaultRepositorySystemSession(RepositorySystemSession session) { setDependencyGraphTransformer(session.getDependencyGraphTransformer()); setData(session.getData()); setCache(session.getCache()); + this.onSessionEndedRegistrar = session::addOnSessionEndedHandler; } @Override @@ -766,6 +775,17 @@ public DefaultRepositorySystemSession setCache(RepositoryCache cache) { return this; } + /** + * Registers onSessionEnded handler, if able to. + * + * @param handler The handler to register + * @return Return {@code true} if registration was possible, otherwise {@code false}. + */ + @Override + public boolean addOnSessionEndedHandler(Runnable handler) { + return onSessionEndedRegistrar.apply(handler); + } + /** * Marks this session as read-only such that any future attempts to call its mutators will fail with an exception. * Marking an already read-only session as read-only has no effect. The session's data and cache remain writable diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java index 7253c2095..9ba73502d 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java @@ -18,6 +18,7 @@ */ package org.eclipse.aether; +import java.io.Closeable; import java.util.Collection; import java.util.List; @@ -65,7 +66,7 @@ * @noimplement This interface is not intended to be implemented by clients. * @noextend This interface is not intended to be extended by clients. */ -public interface RepositorySystem { +public interface RepositorySystem extends Closeable { /** * Expands a version range to a list of matching versions, in ascending order. For example, resolves "[3.8,4.0)" to @@ -277,7 +278,7 @@ List newResolutionRepositories( * {@link #deploy(RepositorySystemSession, DeployRequest) deploy()} is used as is and expected to already carry any * required authentication or proxy configuration. This method can be used to apply the authentication/proxy * configuration from a session to a bare repository definition to obtain the complete repository definition for use - * in the deploy request. + * in the deployment request. * * @param session The repository system session from which to configure the repository, must not be {@code null}. * @param repository The repository prototype from which to derive the deployment repository, must not be @@ -295,6 +296,15 @@ List newResolutionRepositories( */ void addOnSystemEndedHandler(Runnable handler); + /** + * Creates a brand-new session builder instance that produces "top level" (root) session. Top level sessions are + * associated with its creator {@link RepositorySystem} instance, and may be used only with that given instance and + * only within the lifespan of it, and after use should be closed. + * + * @since TBD + */ + RepositorySystemSession.SessionBuilder createSessionBuilder(); + /** * Signals to repository system to shut down. Shut down instance is not usable anymore. *

@@ -304,8 +314,26 @@ List newResolutionRepositories( * When shutdown happens, all the registered on-close handlers will be invoked (even if some throws), and at end * of operation a {@link MultiRuntimeException} may be thrown, signaling that some handler(s) failed. This exception * may be ignored, is at the discretion of caller. + *

+ * Note: this method actually just calls {@link #close()}. * * @since 1.9.0 + * @deprecated Use {@link #close()} instead. */ + @Deprecated void shutdown(); + + /** + * Signals to repository system to shut down. Shut down instance is not usable anymore. + *

+ * Repository system may perform some resource cleanup, if applicable. Not using this method may cause leaks or + * unclean shutdown of some subsystem. + *

+ * When shutdown happens, all the registered on-close handlers will be invoked (even if some throws), and at end + * of operation a {@link MultiRuntimeException} may be thrown, signaling that some handler(s) failed. This exception + * may be ignored, is at the discretion of caller. + * + * @since TBD + */ + void close(); } diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java index c101621ee..606449bd6 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java @@ -18,6 +18,8 @@ */ package org.eclipse.aether; +import java.io.Closeable; +import java.io.File; import java.util.Map; import org.eclipse.aether.artifact.ArtifactTypeRegistry; @@ -31,6 +33,7 @@ import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.MirrorSelector; import org.eclipse.aether.repository.ProxySelector; +import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.repository.WorkspaceReader; import org.eclipse.aether.resolution.ArtifactDescriptorPolicy; @@ -48,6 +51,350 @@ */ public interface RepositorySystemSession { + /** + * Immutable session that is closeable, can have onClose handlers registered and should be handled as a resource. + * These session instances can be created with {@link SessionBuilder}. + * + * @since TBD + */ + interface CloseableRepositorySystemSession extends RepositorySystemSession, Closeable { + /** + * Returns the ID of this closeable session instance. Each closeable session has different ID, unique within + * repository system, that they were created for. + * + * @return The session ID that is never {@code null}. + */ + String sessionId(); + + /** + * Closes the session. The session should be closed by its creator. A closed session should not be used anymore. + * This method may be invoked multiple times, but close will act only once (first time). + */ + @Override + void close(); + } + + /** + * Builder for building {@link CloseableRepositorySystemSession} instances. Builder instances can be created with + * {@link RepositorySystem#createSessionBuilder()} method, and built sessions must be handled as resources + * (closed once done with them). + * + * @since TBD + */ + interface SessionBuilder { + /** + * Controls whether the repository system operates in offline mode and avoids/refuses any access to remote + * repositories. + * + * @param offline {@code true} if the repository system is in offline mode, {@code false} otherwise. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setOffline(boolean offline); + + /** + * Controls whether repositories declared in artifact descriptors should be ignored during transitive dependency + * collection. If enabled, only the repositories originally provided with the collect request will be considered. + * + * @param ignoreArtifactDescriptorRepositories {@code true} to ignore additional repositories from artifact + * descriptors, {@code false} to merge those with the originally + * specified repositories. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setIgnoreArtifactDescriptorRepositories(boolean ignoreArtifactDescriptorRepositories); + + /** + * Sets the policy which controls whether resolutions errors from remote repositories should be cached. + * + * @param resolutionErrorPolicy The resolution error policy for this session, may be {@code null} if resolution + * errors should generally not be cached. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setResolutionErrorPolicy(ResolutionErrorPolicy resolutionErrorPolicy); + + /** + * Sets the policy which controls how errors related to reading artifact descriptors should be handled. + * + * @param artifactDescriptorPolicy The descriptor error policy for this session, may be {@code null} if descriptor + * errors should generally not be tolerated. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setArtifactDescriptorPolicy(ArtifactDescriptorPolicy artifactDescriptorPolicy); + + /** + * Sets the global checksum policy. If set, the global checksum policy overrides the checksum policies of the remote + * repositories being used for resolution. + * + * @param checksumPolicy The global checksum policy, may be {@code null}/empty to apply the per-repository policies. + * @return This session for chaining, never {@code null}. + * @see RepositoryPolicy#CHECKSUM_POLICY_FAIL + * @see RepositoryPolicy#CHECKSUM_POLICY_IGNORE + * @see RepositoryPolicy#CHECKSUM_POLICY_WARN + */ + SessionBuilder setChecksumPolicy(String checksumPolicy); + + /** + * Sets the global update policy. If set, the global update policy overrides the update policies of the remote + * repositories being used for resolution. + *

+ * This method is meant for code that does not want to distinguish between artifact and metadata policies. + * Note: applications should either use get/set updatePolicy (this method and + * {@link RepositorySystemSession#getUpdatePolicy()}) or also distinguish between artifact and + * metadata update policies (and use other methods), but should not mix the two! + * + * @param updatePolicy The global update policy, may be {@code null}/empty to apply the per-repository policies. + * @return This session for chaining, never {@code null}. + * @see RepositoryPolicy#UPDATE_POLICY_ALWAYS + * @see RepositoryPolicy#UPDATE_POLICY_DAILY + * @see RepositoryPolicy#UPDATE_POLICY_NEVER + * @see #setArtifactUpdatePolicy(String) + * @see #setMetadataUpdatePolicy(String) + */ + SessionBuilder setUpdatePolicy(String updatePolicy); + + /** + * Sets the global artifact update policy. If set, the global update policy overrides the artifact update policies + * of the remote repositories being used for resolution. + * + * @param artifactUpdatePolicy The global update policy, may be {@code null}/empty to apply the per-repository policies. + * @return This session for chaining, never {@code null}. + * @see RepositoryPolicy#UPDATE_POLICY_ALWAYS + * @see RepositoryPolicy#UPDATE_POLICY_DAILY + * @see RepositoryPolicy#UPDATE_POLICY_NEVER + * @since TBD + */ + SessionBuilder setArtifactUpdatePolicy(String artifactUpdatePolicy); + + /** + * Sets the global metadata update policy. If set, the global update policy overrides the metadata update policies + * of the remote repositories being used for resolution. + * + * @param metadataUpdatePolicy The global update policy, may be {@code null}/empty to apply the per-repository policies. + * @return This session for chaining, never {@code null}. + * @see RepositoryPolicy#UPDATE_POLICY_ALWAYS + * @see RepositoryPolicy#UPDATE_POLICY_DAILY + * @see RepositoryPolicy#UPDATE_POLICY_NEVER + * @since TBD + */ + SessionBuilder setMetadataUpdatePolicy(String metadataUpdatePolicy); + + /** + * Sets the local repository manager used during this session. Note: Eventually, a valid session must have + * a local repository manager set. + * + * @param localRepositoryManager The local repository manager used during this session, may be {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setLocalRepositoryManager(LocalRepositoryManager localRepositoryManager); + + /** + * Sets the workspace reader used during this session. If set, the workspace reader will usually be consulted first + * to resolve artifacts. + * + * @param workspaceReader The workspace reader for this session, may be {@code null} if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setWorkspaceReader(WorkspaceReader workspaceReader); + + /** + * Sets the listener being notified of actions in the repository system. + * + * @param repositoryListener The repository listener, may be {@code null} if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setRepositoryListener(RepositoryListener repositoryListener); + + /** + * Sets the listener being notified of uploads/downloads by the repository system. + * + * @param transferListener The transfer listener, may be {@code null} if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setTransferListener(TransferListener transferListener); + + /** + * Sets the system properties to use, e.g. for processing of artifact descriptors. System properties are usually + * collected from the runtime environment like {@link System#getProperties()} and environment variables. + *

+ * Note: System properties are of type {@code Map} and any key-value pair in the input map + * that doesn't match this type will be silently ignored. + * + * @param systemProperties The system properties, may be {@code null} or empty if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setSystemProperties(Map systemProperties); + + /** + * Sets the specified system property. + * + * @param key The property key, must not be {@code null}. + * @param value The property value, may be {@code null} to remove/unset the property. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setSystemProperty(String key, String value); + + /** + * Sets the user properties to use, e.g. for processing of artifact descriptors. User properties are similar to + * system properties but are set on the discretion of the user and hence are considered of higher priority than + * system properties in case of conflicts. + *

+ * Note: User properties are of type {@code Map} and any key-value pair in the input map + * that doesn't match this type will be silently ignored. + * + * @param userProperties The user properties, may be {@code null} or empty if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setUserProperties(Map userProperties); + + /** + * Sets the specified user property. + * + * @param key The property key, must not be {@code null}. + * @param value The property value, may be {@code null} to remove/unset the property. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setUserProperty(String key, String value); + + /** + * Sets the configuration properties used to tweak internal aspects of the repository system (e.g. thread pooling, + * connector-specific behavior, etc.). + *

+ * Note: Configuration properties are of type {@code Map} and any key-value pair in the + * input map that doesn't match this type will be silently ignored. + * + * @param configProperties The configuration properties, may be {@code null} or empty if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setConfigProperties(Map configProperties); + + /** + * Sets the specified configuration property. + * + * @param key The property key, must not be {@code null}. + * @param value The property value, may be {@code null} to remove/unset the property. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setConfigProperty(String key, Object value); + + /** + * Sets the mirror selector to use for repositories discovered in artifact descriptors. Note that this selector is + * not used for remote repositories which are passed as request parameters to the repository system, those + * repositories are supposed to denote the effective repositories. + * + * @param mirrorSelector The mirror selector to use, may be {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setMirrorSelector(MirrorSelector mirrorSelector); + + /** + * Sets the proxy selector to use for repositories discovered in artifact descriptors. Note that this selector is + * not used for remote repositories which are passed as request parameters to the repository system, those + * repositories are supposed to have their proxy (if any) already set. + * + * @param proxySelector The proxy selector to use, may be {@code null}. + * @return This session for chaining, never {@code null}. + * @see RemoteRepository#getProxy() + */ + SessionBuilder setProxySelector(ProxySelector proxySelector); + + /** + * Sets the authentication selector to use for repositories discovered in artifact descriptors. Note that this + * selector is not used for remote repositories which are passed as request parameters to the repository system, + * those repositories are supposed to have their authentication (if any) already set. + * + * @param authenticationSelector The authentication selector to use, may be {@code null}. + * @return This session for chaining, never {@code null}. + * @see RemoteRepository#getAuthentication() + */ + SessionBuilder setAuthenticationSelector(AuthenticationSelector authenticationSelector); + + /** + * Sets the registry of artifact types recognized by this session. + * + * @param artifactTypeRegistry The artifact type registry, may be {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setArtifactTypeRegistry(ArtifactTypeRegistry artifactTypeRegistry); + + /** + * Sets the dependency traverser to use for building dependency graphs. + * + * @param dependencyTraverser The dependency traverser to use for building dependency graphs, may be {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setDependencyTraverser(DependencyTraverser dependencyTraverser); + + /** + * Sets the dependency manager to use for building dependency graphs. + * + * @param dependencyManager The dependency manager to use for building dependency graphs, may be {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setDependencyManager(DependencyManager dependencyManager); + + /** + * Sets the dependency selector to use for building dependency graphs. + * + * @param dependencySelector The dependency selector to use for building dependency graphs, may be {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setDependencySelector(DependencySelector dependencySelector); + + /** + * Sets the version filter to use for building dependency graphs. + * + * @param versionFilter The version filter to use for building dependency graphs, may be {@code null} to not filter + * versions. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setVersionFilter(VersionFilter versionFilter); + + /** + * Sets the dependency graph transformer to use for building dependency graphs. + * + * @param dependencyGraphTransformer The dependency graph transformer to use for building dependency graphs, may be + * {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setDependencyGraphTransformer(DependencyGraphTransformer dependencyGraphTransformer); + + /** + * Sets the custom data associated with this session. + * + * @param data The session data, may be {@code null}. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setData(SessionData data); + + /** + * Sets the cache the repository system may use to save data for future reuse during the session. + * + * @param cache The repository cache, may be {@code null} if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder setCache(RepositoryCache cache); + + /** + * Shortcut method to set up local repository manager. + * + * @param basedir The local repository base directory, may be {@code null} if none. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder withLocalRepository(File basedir); + + /** + * Shortcut method to shallow-copy passed in session into current builder. + * + * @param session The session to shallow-copy from. + * @return This session for chaining, never {@code null}. + */ + SessionBuilder withRepositorySystemSession(RepositorySystemSession session); + + /** + * Creates a session instance. + */ + CloseableRepositorySystemSession build(); + } + /** * Indicates whether the repository system operates in offline mode and avoids/refuses any access to remote * repositories. @@ -283,4 +630,17 @@ public interface RepositorySystemSession { * @return The repository cache or {@code null} if none. */ RepositoryCache getCache(); + + /** + * Registers a handler to execute when this session closed. + *

+ * Note: Resolver 1.x sessions will not be able to register handlers. Migrate to Resolver 2.x way of handling + * sessions to make full use of new features. New features (like HTTP/2 transport) depend on this functionality. + * While they will function with Resolver 1.x sessions, they may produce resource leaks. + * + * @param handler the handler, never {@code null}. + * @return {@code true} if handler registered, otherwise false. + * @since TBD + */ + boolean addOnSessionEndedHandler(Runnable handler); } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java index b3a0c44e1..3048ed7e2 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemLifecycle.java @@ -18,6 +18,8 @@ */ package org.eclipse.aether.impl; +import org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession; + /** * Lifecycle managing component for repository system. * @@ -39,4 +41,33 @@ public interface RepositorySystemLifecycle { * Throws if repository system is already shut down. */ void addOnSystemEndedHandler(Runnable handler); + + /** + * Registers the session for lifecycle tracking: it marks that the passed in session instance is about to start. + *

+ * Same session instance can be started only once. + * + * @since TBD + */ + void sessionStarted(CloseableRepositorySystemSession session); + + /** + * Signals that passed in session was ended, it will not be used anymore. Repository system + * will invoke the registered handlers for this session, if any. This method throws if the passed in session + * instance was not passed to method {@link #sessionStarted(CloseableRepositorySystemSession)} beforehand. + *

+ * Same session instance can be ended only once. + * + * @since TBD + */ + void sessionEnded(CloseableRepositorySystemSession session); + + /** + * Registers an "on session end" handler. + *

+ * Throws if session was not passed to {@link #sessionStarted(CloseableRepositorySystemSession)} beforehand. + * + * @since TBD + */ + void addOnSessionEndedHandle(CloseableRepositorySystemSession session, Runnable handler); } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java index 19c897233..1a8c9814d 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -59,6 +60,7 @@ import org.eclipse.aether.installation.InstallRequest; import org.eclipse.aether.installation.InstallResult; import org.eclipse.aether.installation.InstallationException; +import org.eclipse.aether.internal.impl.session.DefaultSessionBuilder; import org.eclipse.aether.repository.Authentication; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.LocalRepositoryManager; @@ -97,7 +99,9 @@ @Singleton @Named public class DefaultRepositorySystem implements RepositorySystem { - private final AtomicBoolean shutdown; + private final AtomicBoolean closed; + + private final AtomicInteger sessionIdCounter; private final VersionResolver versionResolver; @@ -138,7 +142,8 @@ public DefaultRepositorySystem( SyncContextFactory syncContextFactory, RemoteRepositoryManager remoteRepositoryManager, RepositorySystemLifecycle repositorySystemLifecycle) { - this.shutdown = new AtomicBoolean(false); + this.closed = new AtomicBoolean(false); + this.sessionIdCounter = new AtomicInteger(0); this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null"); this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null"); this.artifactResolver = requireNonNull(artifactResolver, "artifact resolver cannot be null"); @@ -393,9 +398,19 @@ public void addOnSystemEndedHandler(Runnable handler) { repositorySystemLifecycle.addOnSystemEndedHandler(handler); } + @Override + public RepositorySystemSession.SessionBuilder createSessionBuilder() { + return new DefaultSessionBuilder(this, repositorySystemLifecycle, "id-" + sessionIdCounter.incrementAndGet()); + } + @Override public void shutdown() { - if (shutdown.compareAndSet(false, true)) { + close(); + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { repositorySystemLifecycle.systemEnded(); } } @@ -411,7 +426,7 @@ private void validateSession(RepositorySystemSession session) { invalidSession(session.getAuthenticationSelector(), "authentication selector"); invalidSession(session.getArtifactTypeRegistry(), "artifact type registry"); invalidSession(session.getData(), "data"); - if (shutdown.get()) { + if (closed.get()) { throw new IllegalStateException("repository system is already shut down"); } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java index 0d462aa8c..853c97209 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java @@ -23,10 +23,12 @@ import javax.inject.Singleton; import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.aether.MultiRuntimeException; +import org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession; import org.eclipse.aether.impl.RepositorySystemLifecycle; import static java.util.Objects.requireNonNull; @@ -41,10 +43,13 @@ public class DefaultRepositorySystemLifecycle implements RepositorySystemLifecyc private final CopyOnWriteArrayList onSystemEndedHandlers; + private final ConcurrentHashMap> onSessionEndedHandlers; + @Inject public DefaultRepositorySystemLifecycle() { this.shutdown = new AtomicBoolean(false); this.onSystemEndedHandlers = new CopyOnWriteArrayList<>(); + this.onSessionEndedHandlers = new ConcurrentHashMap<>(); } @Override @@ -69,6 +74,59 @@ public void addOnSystemEndedHandler(Runnable handler) { onSystemEndedHandlers.add(0, handler); } + @Override + public void sessionStarted(CloseableRepositorySystemSession session) { + requireNonNull(session, "session cannot be null"); + requireNotShutdown(); + String sessionId = session.sessionId(); + onSessionEndedHandlers.compute(sessionId, (k, v) -> { + if (v != null) { + throw new IllegalStateException("session instance already registered"); + } + return new CopyOnWriteArrayList<>(); + }); + } + + @Override + public void sessionEnded(CloseableRepositorySystemSession session) { + requireNonNull(session, "session cannot be null"); + requireNotShutdown(); + String sessionId = session.sessionId(); + ArrayList handlers = new ArrayList<>(); + onSessionEndedHandlers.compute(sessionId, (k, v) -> { + if (v == null) { + throw new IllegalStateException("session instance not registered"); + } + handlers.addAll(v); + return null; + }); + + ArrayList exceptions = new ArrayList<>(); + for (Runnable handler : handlers) { + try { + handler.run(); + } catch (Exception e) { + exceptions.add(e); + } + } + MultiRuntimeException.mayThrow("sessionEnded handler issue(s)", exceptions); + } + + @Override + public void addOnSessionEndedHandle(CloseableRepositorySystemSession session, Runnable handler) { + requireNonNull(session, "session cannot be null"); + requireNonNull(handler, "handler cannot be null"); + requireNotShutdown(); + String sessionId = session.sessionId(); + onSessionEndedHandlers.compute(sessionId, (k, v) -> { + if (v == null) { + throw new IllegalStateException("session instance not registered"); + } + v.add(handler); + return v; + }); + } + private void requireNotShutdown() { if (shutdown.get()) { throw new IllegalStateException("repository system is already shut down"); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java new file mode 100644 index 000000000..f74e75454 --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java @@ -0,0 +1,328 @@ +/* + * 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.session; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.aether.RepositoryCache; +import org.eclipse.aether.RepositoryListener; +import org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession; +import org.eclipse.aether.SessionData; +import org.eclipse.aether.artifact.ArtifactTypeRegistry; +import org.eclipse.aether.collection.DependencyGraphTransformer; +import org.eclipse.aether.collection.DependencyManager; +import org.eclipse.aether.collection.DependencySelector; +import org.eclipse.aether.collection.DependencyTraverser; +import org.eclipse.aether.collection.VersionFilter; +import org.eclipse.aether.impl.RepositorySystemLifecycle; +import org.eclipse.aether.repository.AuthenticationSelector; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.LocalRepositoryManager; +import org.eclipse.aether.repository.MirrorSelector; +import org.eclipse.aether.repository.ProxySelector; +import org.eclipse.aether.repository.WorkspaceReader; +import org.eclipse.aether.resolution.ArtifactDescriptorPolicy; +import org.eclipse.aether.resolution.ResolutionErrorPolicy; +import org.eclipse.aether.transfer.TransferListener; + +import static java.util.Objects.requireNonNull; + +/** + * A default implementation of repository system session that is immutable. + */ +public final class DefaultCloseableRepositorySystemSession implements CloseableRepositorySystemSession { + private final AtomicBoolean closed; + + private final String sessionId; + + private final boolean offline; + + private final boolean ignoreArtifactDescriptorRepositories; + + private final ResolutionErrorPolicy resolutionErrorPolicy; + + private final ArtifactDescriptorPolicy artifactDescriptorPolicy; + + private final String checksumPolicy; + + private final String artifactUpdatePolicy; + + private final String metadataUpdatePolicy; + + private final LocalRepositoryManager localRepositoryManager; + + private final WorkspaceReader workspaceReader; + + private final RepositoryListener repositoryListener; + + private final TransferListener transferListener; + + private final Map systemProperties; + + private final Map userProperties; + + private final Map configProperties; + + private final MirrorSelector mirrorSelector; + + private final ProxySelector proxySelector; + + private final AuthenticationSelector authenticationSelector; + + private final ArtifactTypeRegistry artifactTypeRegistry; + + private final DependencyTraverser dependencyTraverser; + + private final DependencyManager dependencyManager; + + private final DependencySelector dependencySelector; + + private final VersionFilter versionFilter; + + private final DependencyGraphTransformer dependencyGraphTransformer; + + private final SessionData data; + + private final RepositoryCache cache; + + private final RepositorySystemLifecycle repositorySystemLifecycle; + + @SuppressWarnings("checkstyle:parameternumber") + public DefaultCloseableRepositorySystemSession( + String sessionId, + boolean offline, + boolean ignoreArtifactDescriptorRepositories, + ResolutionErrorPolicy resolutionErrorPolicy, + ArtifactDescriptorPolicy artifactDescriptorPolicy, + String checksumPolicy, + String artifactUpdatePolicy, + String metadataUpdatePolicy, + LocalRepositoryManager localRepositoryManager, + WorkspaceReader workspaceReader, + RepositoryListener repositoryListener, + TransferListener transferListener, + Map systemProperties, + Map userProperties, + Map configProperties, + MirrorSelector mirrorSelector, + ProxySelector proxySelector, + AuthenticationSelector authenticationSelector, + ArtifactTypeRegistry artifactTypeRegistry, + DependencyTraverser dependencyTraverser, + DependencyManager dependencyManager, + DependencySelector dependencySelector, + VersionFilter versionFilter, + DependencyGraphTransformer dependencyGraphTransformer, + SessionData data, + RepositoryCache cache, + RepositorySystemLifecycle repositorySystemLifecycle) { + this.closed = new AtomicBoolean(false); + this.sessionId = requireNonNull(sessionId); + this.offline = offline; + this.ignoreArtifactDescriptorRepositories = ignoreArtifactDescriptorRepositories; + this.resolutionErrorPolicy = resolutionErrorPolicy; + this.artifactDescriptorPolicy = artifactDescriptorPolicy; + this.checksumPolicy = checksumPolicy; + this.artifactUpdatePolicy = artifactUpdatePolicy; + this.metadataUpdatePolicy = metadataUpdatePolicy; + this.localRepositoryManager = requireNonNull(localRepositoryManager); + this.workspaceReader = workspaceReader; + this.repositoryListener = repositoryListener; + this.transferListener = transferListener; + this.systemProperties = Collections.unmodifiableMap(systemProperties); + this.userProperties = Collections.unmodifiableMap(userProperties); + this.configProperties = Collections.unmodifiableMap(configProperties); + this.mirrorSelector = requireNonNull(mirrorSelector); + this.proxySelector = requireNonNull(proxySelector); + this.authenticationSelector = requireNonNull(authenticationSelector); + this.artifactTypeRegistry = requireNonNull(artifactTypeRegistry); + this.dependencyTraverser = dependencyTraverser; + this.dependencyManager = dependencyManager; + this.dependencySelector = dependencySelector; + this.versionFilter = versionFilter; + this.dependencyGraphTransformer = dependencyGraphTransformer; + this.data = requireNonNull(data); + this.cache = cache; + this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle); + + repositorySystemLifecycle.sessionStarted(this); + } + + @Override + public String sessionId() { + return sessionId; + } + + @Override + public boolean isOffline() { + return offline; + } + + @Override + public boolean isIgnoreArtifactDescriptorRepositories() { + return ignoreArtifactDescriptorRepositories; + } + + @Override + public ResolutionErrorPolicy getResolutionErrorPolicy() { + return resolutionErrorPolicy; + } + + @Override + public ArtifactDescriptorPolicy getArtifactDescriptorPolicy() { + return artifactDescriptorPolicy; + } + + @Override + public String getChecksumPolicy() { + return checksumPolicy; + } + + @Override + public String getUpdatePolicy() { + return getArtifactUpdatePolicy(); + } + + @Override + public String getArtifactUpdatePolicy() { + return artifactUpdatePolicy; + } + + @Override + public String getMetadataUpdatePolicy() { + return metadataUpdatePolicy; + } + + @Override + public LocalRepository getLocalRepository() { + return getLocalRepositoryManager().getRepository(); + } + + @Override + public LocalRepositoryManager getLocalRepositoryManager() { + return localRepositoryManager; + } + + @Override + public WorkspaceReader getWorkspaceReader() { + return workspaceReader; + } + + @Override + public RepositoryListener getRepositoryListener() { + return repositoryListener; + } + + @Override + public TransferListener getTransferListener() { + return transferListener; + } + + @Override + public Map getSystemProperties() { + return systemProperties; + } + + @Override + public Map getUserProperties() { + return userProperties; + } + + @Override + public Map getConfigProperties() { + return configProperties; + } + + @Override + public MirrorSelector getMirrorSelector() { + return mirrorSelector; + } + + @Override + public ProxySelector getProxySelector() { + return proxySelector; + } + + @Override + public AuthenticationSelector getAuthenticationSelector() { + return authenticationSelector; + } + + @Override + public ArtifactTypeRegistry getArtifactTypeRegistry() { + return artifactTypeRegistry; + } + + @Override + public DependencyTraverser getDependencyTraverser() { + return dependencyTraverser; + } + + @Override + public DependencyManager getDependencyManager() { + return dependencyManager; + } + + @Override + public DependencySelector getDependencySelector() { + return dependencySelector; + } + + @Override + public VersionFilter getVersionFilter() { + return versionFilter; + } + + @Override + public DependencyGraphTransformer getDependencyGraphTransformer() { + return dependencyGraphTransformer; + } + + @Override + public SessionData getData() { + return data; + } + + @Override + public RepositoryCache getCache() { + return cache; + } + + @Override + public boolean addOnSessionEndedHandler(Runnable handler) { + throwIfClosed(); + repositorySystemLifecycle.addOnSessionEndedHandle(this, handler); + return true; + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + repositorySystemLifecycle.sessionEnded(this); + } + } + + private void throwIfClosed() { + if (closed.get()) { + throw new IllegalStateException("Session " + sessionId + " already closed"); + } + } +} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java new file mode 100644 index 000000000..ba33beceb --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java @@ -0,0 +1,566 @@ +/* + * 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.session; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.aether.DefaultSessionData; +import org.eclipse.aether.RepositoryCache; +import org.eclipse.aether.RepositoryListener; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.RepositorySystemSession.SessionBuilder; +import org.eclipse.aether.SessionData; +import org.eclipse.aether.artifact.ArtifactTypeRegistry; +import org.eclipse.aether.collection.DependencyGraphTransformer; +import org.eclipse.aether.collection.DependencyManager; +import org.eclipse.aether.collection.DependencySelector; +import org.eclipse.aether.collection.DependencyTraverser; +import org.eclipse.aether.collection.VersionFilter; +import org.eclipse.aether.impl.RepositorySystemLifecycle; +import org.eclipse.aether.repository.AuthenticationSelector; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.LocalRepositoryManager; +import org.eclipse.aether.repository.MirrorSelector; +import org.eclipse.aether.repository.ProxySelector; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.WorkspaceReader; +import org.eclipse.aether.resolution.ArtifactDescriptorPolicy; +import org.eclipse.aether.resolution.ResolutionErrorPolicy; +import org.eclipse.aether.transfer.TransferListener; + +import static java.util.Objects.requireNonNull; + +/** + * A default implementation of session builder. + */ +public final class DefaultSessionBuilder implements SessionBuilder, RepositorySystemSession { + private static final MirrorSelector NULL_MIRROR_SELECTOR = r -> null; + + private static final ProxySelector NULL_PROXY_SELECTOR = RemoteRepository::getProxy; + + private static final AuthenticationSelector NULL_AUTHENTICATION_SELECTOR = RemoteRepository::getAuthentication; + + private static final ArtifactTypeRegistry NULL_ARTIFACT_TYPE_REGISTRY = t -> null; + + private final RepositorySystem repositorySystem; + + private final RepositorySystemLifecycle repositorySystemLifecycle; + + private final String sessionId; + + private final ArrayList onCloseHandler; + + private boolean offline; + + private boolean ignoreArtifactDescriptorRepositories; + + private ResolutionErrorPolicy resolutionErrorPolicy; + + private ArtifactDescriptorPolicy artifactDescriptorPolicy; + + private String checksumPolicy; + + private String artifactUpdatePolicy; + + private String metadataUpdatePolicy; + + private LocalRepositoryManager localRepositoryManager; + + private WorkspaceReader workspaceReader; + + private RepositoryListener repositoryListener; + + private TransferListener transferListener; + + private Map systemProperties = new HashMap<>(); + + private Map userProperties = new HashMap<>(); + + private Map configProperties = new HashMap<>(); + + private MirrorSelector mirrorSelector = NULL_MIRROR_SELECTOR; + + private ProxySelector proxySelector = NULL_PROXY_SELECTOR; + + private AuthenticationSelector authenticationSelector = NULL_AUTHENTICATION_SELECTOR; + + private ArtifactTypeRegistry artifactTypeRegistry = NULL_ARTIFACT_TYPE_REGISTRY; + + private DependencyTraverser dependencyTraverser; + + private DependencyManager dependencyManager; + + private DependencySelector dependencySelector; + + private VersionFilter versionFilter; + + private DependencyGraphTransformer dependencyGraphTransformer; + + private SessionData data = new DefaultSessionData(); + + private RepositoryCache cache; + + public DefaultSessionBuilder( + RepositorySystem repositorySystem, RepositorySystemLifecycle repositorySystemLifecycle, String sessionId) { + this.repositorySystem = requireNonNull(repositorySystem); + this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle); + this.sessionId = requireNonNull(sessionId); + this.onCloseHandler = new ArrayList<>(); + } + + @Override + public boolean isOffline() { + return offline; + } + + @Override + public boolean isIgnoreArtifactDescriptorRepositories() { + return ignoreArtifactDescriptorRepositories; + } + + @Override + public ResolutionErrorPolicy getResolutionErrorPolicy() { + return resolutionErrorPolicy; + } + + @Override + public ArtifactDescriptorPolicy getArtifactDescriptorPolicy() { + return artifactDescriptorPolicy; + } + + @Override + public String getChecksumPolicy() { + return checksumPolicy; + } + + @Override + public String getUpdatePolicy() { + return getArtifactUpdatePolicy(); + } + + @Override + public String getArtifactUpdatePolicy() { + return artifactUpdatePolicy; + } + + @Override + public String getMetadataUpdatePolicy() { + return metadataUpdatePolicy; + } + + @Override + public LocalRepository getLocalRepository() { + return localRepositoryManager.getRepository(); + } + + @Override + public LocalRepositoryManager getLocalRepositoryManager() { + return localRepositoryManager; + } + + @Override + public WorkspaceReader getWorkspaceReader() { + return workspaceReader; + } + + @Override + public RepositoryListener getRepositoryListener() { + return repositoryListener; + } + + @Override + public TransferListener getTransferListener() { + return transferListener; + } + + @Override + public Map getSystemProperties() { + return systemProperties; + } + + @Override + public Map getUserProperties() { + return userProperties; + } + + @Override + public Map getConfigProperties() { + return configProperties; + } + + @Override + public MirrorSelector getMirrorSelector() { + return mirrorSelector; + } + + @Override + public ProxySelector getProxySelector() { + return proxySelector; + } + + @Override + public AuthenticationSelector getAuthenticationSelector() { + return authenticationSelector; + } + + @Override + public ArtifactTypeRegistry getArtifactTypeRegistry() { + return artifactTypeRegistry; + } + + @Override + public DependencyTraverser getDependencyTraverser() { + return dependencyTraverser; + } + + @Override + public DependencyManager getDependencyManager() { + return dependencyManager; + } + + @Override + public DependencySelector getDependencySelector() { + return dependencySelector; + } + + @Override + public VersionFilter getVersionFilter() { + return versionFilter; + } + + @Override + public DependencyGraphTransformer getDependencyGraphTransformer() { + return dependencyGraphTransformer; + } + + @Override + public SessionData getData() { + return data; + } + + @Override + public RepositoryCache getCache() { + return cache; + } + + @Override + public boolean addOnSessionEndedHandler(Runnable handler) { + requireNonNull(handler, "null handler"); + onCloseHandler.add(handler); + return true; + } + + @Override + public DefaultSessionBuilder setOffline(boolean offline) { + this.offline = offline; + return this; + } + + @Override + public DefaultSessionBuilder setIgnoreArtifactDescriptorRepositories(boolean ignoreArtifactDescriptorRepositories) { + this.ignoreArtifactDescriptorRepositories = ignoreArtifactDescriptorRepositories; + return this; + } + + @Override + public DefaultSessionBuilder setResolutionErrorPolicy(ResolutionErrorPolicy resolutionErrorPolicy) { + this.resolutionErrorPolicy = resolutionErrorPolicy; + return this; + } + + @Override + public DefaultSessionBuilder setArtifactDescriptorPolicy(ArtifactDescriptorPolicy artifactDescriptorPolicy) { + this.artifactDescriptorPolicy = artifactDescriptorPolicy; + return this; + } + + @Override + public DefaultSessionBuilder setChecksumPolicy(String checksumPolicy) { + this.checksumPolicy = checksumPolicy; + return this; + } + + @Override + public DefaultSessionBuilder setUpdatePolicy(String updatePolicy) { + setArtifactUpdatePolicy(updatePolicy); + setMetadataUpdatePolicy(updatePolicy); + return this; + } + + @Override + public DefaultSessionBuilder setArtifactUpdatePolicy(String artifactUpdatePolicy) { + this.artifactUpdatePolicy = artifactUpdatePolicy; + return this; + } + + @Override + public DefaultSessionBuilder setMetadataUpdatePolicy(String metadataUpdatePolicy) { + this.metadataUpdatePolicy = metadataUpdatePolicy; + return this; + } + + @Override + public DefaultSessionBuilder setLocalRepositoryManager(LocalRepositoryManager localRepositoryManager) { + this.localRepositoryManager = localRepositoryManager; + return this; + } + + @Override + public DefaultSessionBuilder setWorkspaceReader(WorkspaceReader workspaceReader) { + this.workspaceReader = workspaceReader; + return this; + } + + @Override + public DefaultSessionBuilder setRepositoryListener(RepositoryListener repositoryListener) { + this.repositoryListener = repositoryListener; + return this; + } + + @Override + public DefaultSessionBuilder setTransferListener(TransferListener transferListener) { + this.transferListener = transferListener; + return this; + } + + @Override + public DefaultSessionBuilder setSystemProperties(Map systemProperties) { + this.systemProperties = copySafe(systemProperties, String.class); + return this; + } + + @Override + public DefaultSessionBuilder setSystemProperty(String key, String value) { + if (value != null) { + systemProperties.put(key, value); + } else { + systemProperties.remove(key); + } + return this; + } + + @Override + public DefaultSessionBuilder setUserProperties(Map userProperties) { + this.userProperties = copySafe(userProperties, String.class); + return this; + } + + @Override + public DefaultSessionBuilder setUserProperty(String key, String value) { + if (value != null) { + userProperties.put(key, value); + } else { + userProperties.remove(key); + } + return this; + } + + @Override + public DefaultSessionBuilder setConfigProperties(Map configProperties) { + this.configProperties = copySafe(configProperties, Object.class); + return this; + } + + @Override + public DefaultSessionBuilder setConfigProperty(String key, Object value) { + if (value != null) { + configProperties.put(key, value); + } else { + configProperties.remove(key); + } + return this; + } + + @Override + public DefaultSessionBuilder setMirrorSelector(MirrorSelector mirrorSelector) { + this.mirrorSelector = mirrorSelector; + if (this.mirrorSelector == null) { + this.mirrorSelector = NULL_MIRROR_SELECTOR; + } + return this; + } + + @Override + public DefaultSessionBuilder setProxySelector(ProxySelector proxySelector) { + this.proxySelector = proxySelector; + if (this.proxySelector == null) { + this.proxySelector = NULL_PROXY_SELECTOR; + } + return this; + } + + @Override + public DefaultSessionBuilder setAuthenticationSelector(AuthenticationSelector authenticationSelector) { + this.authenticationSelector = authenticationSelector; + if (this.authenticationSelector == null) { + this.authenticationSelector = NULL_AUTHENTICATION_SELECTOR; + } + return this; + } + + @Override + public DefaultSessionBuilder setArtifactTypeRegistry(ArtifactTypeRegistry artifactTypeRegistry) { + this.artifactTypeRegistry = artifactTypeRegistry; + if (this.artifactTypeRegistry == null) { + this.artifactTypeRegistry = NULL_ARTIFACT_TYPE_REGISTRY; + } + return this; + } + + @Override + public DefaultSessionBuilder setDependencyTraverser(DependencyTraverser dependencyTraverser) { + this.dependencyTraverser = dependencyTraverser; + return this; + } + + @Override + public DefaultSessionBuilder setDependencyManager(DependencyManager dependencyManager) { + this.dependencyManager = dependencyManager; + return this; + } + + @Override + public DefaultSessionBuilder setDependencySelector(DependencySelector dependencySelector) { + this.dependencySelector = dependencySelector; + return this; + } + + @Override + public DefaultSessionBuilder setVersionFilter(VersionFilter versionFilter) { + this.versionFilter = versionFilter; + return this; + } + + @Override + public DefaultSessionBuilder setDependencyGraphTransformer(DependencyGraphTransformer dependencyGraphTransformer) { + this.dependencyGraphTransformer = dependencyGraphTransformer; + return this; + } + + @Override + public DefaultSessionBuilder setData(SessionData data) { + this.data = data; + if (this.data == null) { + this.data = new DefaultSessionData(); + } + return this; + } + + @Override + public DefaultSessionBuilder setCache(RepositoryCache cache) { + this.cache = cache; + return this; + } + + @Override + public SessionBuilder withLocalRepository(File basedir) { + LocalRepository localRepository = new LocalRepository(basedir, "default"); + this.localRepositoryManager = repositorySystem.newLocalRepositoryManager(this, localRepository); + return this; + } + + @Override + public SessionBuilder withRepositorySystemSession(RepositorySystemSession session) { + requireNonNull(session, "repository system session cannot be null"); + setOffline(session.isOffline()); + setIgnoreArtifactDescriptorRepositories(session.isIgnoreArtifactDescriptorRepositories()); + setResolutionErrorPolicy(session.getResolutionErrorPolicy()); + setArtifactDescriptorPolicy(session.getArtifactDescriptorPolicy()); + setChecksumPolicy(session.getChecksumPolicy()); + setUpdatePolicy(session.getUpdatePolicy()); + setMetadataUpdatePolicy(session.getMetadataUpdatePolicy()); + setLocalRepositoryManager(session.getLocalRepositoryManager()); + setWorkspaceReader(session.getWorkspaceReader()); + setRepositoryListener(session.getRepositoryListener()); + setTransferListener(session.getTransferListener()); + setSystemProperties(session.getSystemProperties()); + setUserProperties(session.getUserProperties()); + setConfigProperties(session.getConfigProperties()); + setMirrorSelector(session.getMirrorSelector()); + setProxySelector(session.getProxySelector()); + setAuthenticationSelector(session.getAuthenticationSelector()); + setArtifactTypeRegistry(session.getArtifactTypeRegistry()); + setDependencyTraverser(session.getDependencyTraverser()); + setDependencyManager(session.getDependencyManager()); + setDependencySelector(session.getDependencySelector()); + setVersionFilter(session.getVersionFilter()); + setDependencyGraphTransformer(session.getDependencyGraphTransformer()); + setData(session.getData()); + setCache(session.getCache()); + return this; + } + + @Override + public CloseableRepositorySystemSession build() { + CloseableRepositorySystemSession result = new DefaultCloseableRepositorySystemSession( + sessionId, + offline, + ignoreArtifactDescriptorRepositories, + resolutionErrorPolicy, + artifactDescriptorPolicy, + checksumPolicy, + artifactUpdatePolicy, + metadataUpdatePolicy, + localRepositoryManager, + workspaceReader, + repositoryListener, + transferListener, + systemProperties, + userProperties, + configProperties, + mirrorSelector, + proxySelector, + authenticationSelector, + artifactTypeRegistry, + dependencyTraverser, + dependencyManager, + dependencySelector, + versionFilter, + dependencyGraphTransformer, + data, + cache, + repositorySystemLifecycle); + onCloseHandler.forEach(result::addOnSessionEndedHandler); + return result; + } + + @SuppressWarnings("checkstyle:magicnumber") + private static Map copySafe(Map table, Class valueType) { + Map map; + if (table == null || table.isEmpty()) { + map = new HashMap<>(); + } else { + map = new HashMap<>((int) (table.size() / 0.75f) + 1); + for (Map.Entry entry : table.entrySet()) { + Object key = entry.getKey(); + if (key instanceof String) { + Object value = entry.getValue(); + if (valueType.isInstance(value)) { + map.put(key.toString(), valueType.cast(value)); + } + } + } + } + return map; + } +} diff --git a/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java b/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java index 38f1eb6d6..759019b78 100644 --- a/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java +++ b/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java @@ -130,7 +130,7 @@ /** * A simple {@link Supplier} of {@link org.eclipse.aether.RepositorySystem} instances, that on each call supplies newly - * constructed instance. For proper shut down, use {@link RepositorySystem#shutdown()} method on supplied instance(s). + * constructed instance. For proper shut down, use {@link RepositorySystem#close()} method on supplied instance(s). *

* Extend this class and override methods to customize, if needed. * diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml index b8f544d8c..49e2bcf47 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml @@ -40,6 +40,10 @@ + + org.slf4j + slf4j-api + org.apache.maven.resolver maven-resolver-api diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporter.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporter.java index 2200aa870..36693aeb8 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporter.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporter.java @@ -61,6 +61,8 @@ import org.eclipse.aether.transfer.NoTransporterException; import org.eclipse.aether.util.ConfigUtils; import org.eclipse.aether.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * JDK Transport using {@link HttpClient}. @@ -350,6 +352,8 @@ private static Map extractNexus2Checksums(HttpResponse respon */ static final String HTTP_INSTANCE_KEY_PREFIX = JdkTransporterFactory.class.getName() + ".http."; + static final Logger LOGGER = LoggerFactory.getLogger(JdkHttpTransporter.class); + private static HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepository repository) throws NoTransporterException { final String instanceKey = HTTP_INSTANCE_KEY_PREFIX + repository.getId(); @@ -417,7 +421,14 @@ protected PasswordAuthentication getPasswordAuthentication() { }); } - return builder.build(); + HttpClient result = builder.build(); + // Only possible in Java21 + // if (!session.addOnSessionEndedHandler(result::close)) { + // LOGGER.warn( + // "Using Resolver 2 feature without Resolver 2 session handling, you may leak + // resources."); + // } + return result; } catch (NoSuchAlgorithmException e) { throw new WrapperEx(e); } diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml index ff0847481..ab221e91c 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/pom.xml @@ -38,6 +38,10 @@ + + org.slf4j + slf4j-api + org.apache.maven.resolver maven-resolver-api diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java index 80dd1d656..81b8c34f9 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-8/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporterFactory.java @@ -25,6 +25,8 @@ import org.eclipse.aether.spi.connector.transport.Transporter; import org.eclipse.aether.spi.connector.transport.TransporterFactory; import org.eclipse.aether.transfer.NoTransporterException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static java.util.Objects.requireNonNull; @@ -37,6 +39,8 @@ public final class JdkTransporterFactory implements TransporterFactory { public static final String NAME = "jdk"; + private static final Logger LOGGER = LoggerFactory.getLogger(JdkTransporterFactory.class); + private float priority = Float.MIN_VALUE; @Override @@ -55,6 +59,7 @@ public Transporter newInstance(RepositorySystemSession session, RemoteRepository requireNonNull(session, "session cannot be null"); requireNonNull(repository, "repository cannot be null"); + LOGGER.debug("Needs Java11+ to function"); throw new NoTransporterException(repository, "JDK Transport needs Java11+"); } } diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml index f6b32f9a7..9881b5813 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk/pom.xml @@ -38,6 +38,10 @@ + + org.slf4j + slf4j-api + org.apache.maven.resolver maven-resolver-api diff --git a/maven-resolver-transport-jetty/pom.xml b/maven-resolver-transport-jetty/pom.xml index e9c2b6303..3243d082e 100644 --- a/maven-resolver-transport-jetty/pom.xml +++ b/maven-resolver-transport-jetty/pom.xml @@ -41,6 +41,10 @@ + + org.slf4j + slf4j-api + org.apache.maven.resolver maven-resolver-api diff --git a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java index 56f8d93eb..de71e68d9 100644 --- a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java +++ b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java @@ -62,6 +62,8 @@ import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A transporter for HTTP/HTTPS. @@ -346,6 +348,8 @@ protected void implClose() { */ static final String JETTY_INSTANCE_KEY_PREFIX = JettyTransporterFactory.class.getName() + ".jetty."; + static final Logger LOGGER = LoggerFactory.getLogger(JettyTransporter.class); + @SuppressWarnings("checkstyle:methodlength") private static HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepository repository) throws NoTransporterException { @@ -431,6 +435,16 @@ private static HttpClient getOrCreateClient(RepositorySystemSession session, Rem } } } + if (!session.addOnSessionEndedHandler(() -> { + try { + httpClient.stop(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })) { + LOGGER.warn( + "Using Resolver 2 feature without Resolver 2 session handling, you may leak resources."); + } httpClient.start(); return httpClient; } catch (Exception e) { From a0e32b0704983187d2c0e654c2bea5a52879c752 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 17:21:47 +0100 Subject: [PATCH 02/13] Add doco --- src/site/markdown/upgrading-resolver.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/site/markdown/upgrading-resolver.md b/src/site/markdown/upgrading-resolver.md index 454bbbefb..6b221d5a4 100644 --- a/src/site/markdown/upgrading-resolver.md +++ b/src/site/markdown/upgrading-resolver.md @@ -24,3 +24,26 @@ another major version of Resolver. Maven Resolver upcoming major version 2.x should be "smooth sailing", as long you do not depend (directly or indirectly) on **deprecated** classes from Resolver 1.x line. Always use latest 1.x release to check for deprecated classes. + +## Session handling changes + +Maven Resolver 2.x introduced "onSessionEnd" hooks, that became required for +some new features (like HTTP/2 transports are). While existing "Resolver 1.x" +way of handling session will still work, they may produce resource leaks, +and client code "managing" Resolver (like Maven) are strongly advised to upgrade +their session handling. Client code "using" Resolver (like Maven Mojos) +does not have to change anything, they should be able to continue to +function in very same way as before. + +What changed on surface: +* introduction of `RepositorySystemSession` nested interfaces `CloseableRepositorySystemSession` and `SessionBuilder`. +* introduction of `RepositorySystem` new method `createSessionBuilder` that creates `SessionBuilder` instances. +* deprecation of `DefaultRepositorySystemSession` default constructor, this construct is actually the "Resolver 1.x way" of using sessions. + +Required changes in client code managing Resolver 2.x: +* do not use `DefaultRepositorySystemSession` default constructor anymore. +* instead, use `RepositorySystem#createSessionBuilder` to create `SessionBuilder` and out of it `CloseableRepositorySystemSession` instances. +* handle sessions as resources: each created instance should be closed once finished their use. +* session instances created by given `RepositorySystem` should be used only with that same instance. +* to shallow-copy session instances without needing to close them, using existing `DefaultRepositorySystemSession` copy constructor is acceptable (this is what Mojos do usually). +* to shallow-copy existing sessions use `SessionBuilder#withRepositorySystemSession` (this is a new way, instead of `DefaultRepositorySystemSession` copy constructor, when you want new session lifecycle as well). From 34b586483a403505a4bd14a36a5bff3db9842a71 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 17:30:33 +0100 Subject: [PATCH 03/13] Tidy up --- src/site/markdown/upgrading-resolver.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/site/markdown/upgrading-resolver.md b/src/site/markdown/upgrading-resolver.md index 6b221d5a4..5a9489966 100644 --- a/src/site/markdown/upgrading-resolver.md +++ b/src/site/markdown/upgrading-resolver.md @@ -28,22 +28,22 @@ do not depend (directly or indirectly) on **deprecated** classes from Resolver ## Session handling changes Maven Resolver 2.x introduced "onSessionEnd" hooks, that became required for -some new features (like HTTP/2 transports are). While existing "Resolver 1.x" -way of handling session will still work, they may produce resource leaks, -and client code "managing" Resolver (like Maven) are strongly advised to upgrade -their session handling. Client code "using" Resolver (like Maven Mojos) -does not have to change anything, they should be able to continue to -function in very same way as before. +some of the new features (like HTTP/2 transports are). While existing "Resolver 1.x" +way of handling session will still work, it may produce resource leaks. +Client code **managing Resolver** (like Maven) os strongly advised to upgrade +session handling. Client code **using Resolver** (like Maven Mojos) +do not have to change anything, they should be able to continue to +function in very same way as before (as with Resolver 1.x). What changed on surface: * introduction of `RepositorySystemSession` nested interfaces `CloseableRepositorySystemSession` and `SessionBuilder`. * introduction of `RepositorySystem` new method `createSessionBuilder` that creates `SessionBuilder` instances. -* deprecation of `DefaultRepositorySystemSession` default constructor, this construct is actually the "Resolver 1.x way" of using sessions. +* deprecation of `DefaultRepositorySystemSession` default constructor, this constructor is actually the "Resolver 1.x way" of using sessions. -Required changes in client code managing Resolver 2.x: +Required changes in **client code managing Resolver 2.x**: * do not use `DefaultRepositorySystemSession` default constructor anymore. * instead, use `RepositorySystem#createSessionBuilder` to create `SessionBuilder` and out of it `CloseableRepositorySystemSession` instances. * handle sessions as resources: each created instance should be closed once finished their use. * session instances created by given `RepositorySystem` should be used only with that same instance. -* to shallow-copy session instances without needing to close them, using existing `DefaultRepositorySystemSession` copy constructor is acceptable (this is what Mojos do usually). -* to shallow-copy existing sessions use `SessionBuilder#withRepositorySystemSession` (this is a new way, instead of `DefaultRepositorySystemSession` copy constructor, when you want new session lifecycle as well). +* to shallow-copy session instances using existing `DefaultRepositorySystemSession` copy constructor is acceptable (this is what Mojos do). +* to shallow-copy session instances but have new lifecycle as well, use `SessionBuilder#withRepositorySystemSession`. From 37b128135648b69a72e320fff94ff9c24a21ae2f Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 17:35:30 +0100 Subject: [PATCH 04/13] PR Comment --- .../src/main/java/org/eclipse/aether/RepositorySystem.java | 1 + 1 file changed, 1 insertion(+) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java index 9ba73502d..2d092d595 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java @@ -335,5 +335,6 @@ List newResolutionRepositories( * * @since TBD */ + @Override void close(); } From e6d5556d4a3ca2ee3af6b1ce65cbaa0272131b32 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 17:54:12 +0100 Subject: [PATCH 05/13] Typo --- src/site/markdown/upgrading-resolver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/site/markdown/upgrading-resolver.md b/src/site/markdown/upgrading-resolver.md index 5a9489966..d5d2697a0 100644 --- a/src/site/markdown/upgrading-resolver.md +++ b/src/site/markdown/upgrading-resolver.md @@ -30,7 +30,7 @@ do not depend (directly or indirectly) on **deprecated** classes from Resolver Maven Resolver 2.x introduced "onSessionEnd" hooks, that became required for some of the new features (like HTTP/2 transports are). While existing "Resolver 1.x" way of handling session will still work, it may produce resource leaks. -Client code **managing Resolver** (like Maven) os strongly advised to upgrade +Client code **managing Resolver** (like Maven) is strongly advised to upgrade session handling. Client code **using Resolver** (like Maven Mojos) do not have to change anything, they should be able to continue to function in very same way as before (as with Resolver 1.x). From 98ab021ef83f2c95e30f61a7e542ddcc3ebb8d90 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 18:01:22 +0100 Subject: [PATCH 06/13] Error out when system shut down with sessions left open. --- .../impl/DefaultRepositorySystemLifecycle.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java index 853c97209..73a25c7a6 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java @@ -23,6 +23,7 @@ import javax.inject.Singleton; import java.util.ArrayList; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; @@ -54,8 +55,21 @@ public DefaultRepositorySystemLifecycle() { @Override public void systemEnded() { + final ArrayList exceptions = new ArrayList<>(); if (shutdown.compareAndSet(false, true)) { - final ArrayList exceptions = new ArrayList<>(); + for (Map.Entry> sessionEndedHandlers : + onSessionEndedHandlers.entrySet()) { + IllegalStateException sessionNotClosed = + new IllegalStateException("Session " + sessionEndedHandlers.getKey() + " not closed"); + exceptions.add(sessionNotClosed); + for (Runnable onCloseHandler : sessionEndedHandlers.getValue()) { + try { + onCloseHandler.run(); + } catch (Exception e) { + sessionNotClosed.addSuppressed(e); + } + } + } for (Runnable onCloseHandler : onSystemEndedHandlers) { try { onCloseHandler.run(); From 470bc9038e36707b3f6c41561f3c1d3f52f55d6f Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 18:52:51 +0100 Subject: [PATCH 07/13] Add new copy method --- .../aether/RepositorySystemSession.java | 15 +++++++++++- .../impl/DefaultRepositorySystem.java | 3 ++- ...faultCloseableRepositorySystemSession.java | 23 +++++++++++++++---- .../impl/session/DefaultSessionBuilder.java | 11 ++++++++- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java index 606449bd6..e9a3af058 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java @@ -66,6 +66,19 @@ interface CloseableRepositorySystemSession extends RepositorySystemSession, Clos */ String sessionId(); + /** + * Copies this session into a pre-populated builder, effectively making a mutable copy of itself, builder builds + * same session. Important: this session remains unchanged upon return of this method but + * this session and returned builder created session will have same identity. It is up to client code, + * will it close only the original (this) session or new session, or both. Important is, that at least one of + * the sessions must be closed, and consequence is that once either one is closed, the other session is closed + * as well. + *

+ * This pattern should be applied in "filter" like constructs, when code needs to alter the incoming session and + * subsequently pass it downstream. + */ + SessionBuilder copy(); + /** * Closes the session. The session should be closed by its creator. A closed session should not be used anymore. * This method may be invoked multiple times, but close will act only once (first time). @@ -639,7 +652,7 @@ interface SessionBuilder { * While they will function with Resolver 1.x sessions, they may produce resource leaks. * * @param handler the handler, never {@code null}. - * @return {@code true} if handler registered, otherwise false. + * @return {@code true} if handler successfully registered, {@code false} otherwise. * @since TBD */ boolean addOnSessionEndedHandler(Runnable handler); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java index 1a8c9814d..131929bfc 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java @@ -400,7 +400,8 @@ public void addOnSystemEndedHandler(Runnable handler) { @Override public RepositorySystemSession.SessionBuilder createSessionBuilder() { - return new DefaultSessionBuilder(this, repositorySystemLifecycle, "id-" + sessionIdCounter.incrementAndGet()); + return new DefaultSessionBuilder( + this, repositorySystemLifecycle, "id-" + sessionIdCounter.incrementAndGet(), null); } @Override diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java index f74e75454..a9bc520da 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java @@ -24,6 +24,7 @@ import org.eclipse.aether.RepositoryCache; import org.eclipse.aether.RepositoryListener; +import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession; import org.eclipse.aether.SessionData; import org.eclipse.aether.artifact.ArtifactTypeRegistry; @@ -49,10 +50,10 @@ * A default implementation of repository system session that is immutable. */ public final class DefaultCloseableRepositorySystemSession implements CloseableRepositorySystemSession { - private final AtomicBoolean closed; - private final String sessionId; + private final AtomicBoolean closed; + private final boolean offline; private final boolean ignoreArtifactDescriptorRepositories; @@ -103,11 +104,14 @@ public final class DefaultCloseableRepositorySystemSession implements CloseableR private final RepositoryCache cache; + private final RepositorySystem repositorySystem; + private final RepositorySystemLifecycle repositorySystemLifecycle; @SuppressWarnings("checkstyle:parameternumber") public DefaultCloseableRepositorySystemSession( String sessionId, + AtomicBoolean closed, boolean offline, boolean ignoreArtifactDescriptorRepositories, ResolutionErrorPolicy resolutionErrorPolicy, @@ -133,9 +137,10 @@ public DefaultCloseableRepositorySystemSession( DependencyGraphTransformer dependencyGraphTransformer, SessionData data, RepositoryCache cache, + RepositorySystem repositorySystem, RepositorySystemLifecycle repositorySystemLifecycle) { - this.closed = new AtomicBoolean(false); this.sessionId = requireNonNull(sessionId); + this.closed = closed == null ? new AtomicBoolean(false) : closed; this.offline = offline; this.ignoreArtifactDescriptorRepositories = ignoreArtifactDescriptorRepositories; this.resolutionErrorPolicy = resolutionErrorPolicy; @@ -161,9 +166,13 @@ public DefaultCloseableRepositorySystemSession( this.dependencyGraphTransformer = dependencyGraphTransformer; this.data = requireNonNull(data); this.cache = cache; + + this.repositorySystem = requireNonNull(repositorySystem); this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle); - repositorySystemLifecycle.sessionStarted(this); + if (closed == null) { + repositorySystemLifecycle.sessionStarted(this); + } } @Override @@ -171,6 +180,12 @@ public String sessionId() { return sessionId; } + @Override + public SessionBuilder copy() { + return new DefaultSessionBuilder(repositorySystem, repositorySystemLifecycle, sessionId, closed) + .withRepositorySystemSession(this); + } + @Override public boolean isOffline() { return offline; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java index ba33beceb..b58cbc96a 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.aether.DefaultSessionData; import org.eclipse.aether.RepositoryCache; @@ -68,6 +69,8 @@ public final class DefaultSessionBuilder implements SessionBuilder, RepositorySy private final String sessionId; + private final AtomicBoolean closed; + private final ArrayList onCloseHandler; private boolean offline; @@ -121,10 +124,14 @@ public final class DefaultSessionBuilder implements SessionBuilder, RepositorySy private RepositoryCache cache; public DefaultSessionBuilder( - RepositorySystem repositorySystem, RepositorySystemLifecycle repositorySystemLifecycle, String sessionId) { + RepositorySystem repositorySystem, + RepositorySystemLifecycle repositorySystemLifecycle, + String sessionId, + AtomicBoolean closed) { this.repositorySystem = requireNonNull(repositorySystem); this.repositorySystemLifecycle = requireNonNull(repositorySystemLifecycle); this.sessionId = requireNonNull(sessionId); + this.closed = closed; this.onCloseHandler = new ArrayList<>(); } @@ -514,6 +521,7 @@ public SessionBuilder withRepositorySystemSession(RepositorySystemSession sessio public CloseableRepositorySystemSession build() { CloseableRepositorySystemSession result = new DefaultCloseableRepositorySystemSession( sessionId, + closed, offline, ignoreArtifactDescriptorRepositories, resolutionErrorPolicy, @@ -539,6 +547,7 @@ public CloseableRepositorySystemSession build() { dependencyGraphTransformer, data, cache, + repositorySystem, repositorySystemLifecycle); onCloseHandler.forEach(result::addOnSessionEndedHandler); return result; From e4e4ea9f205e17d5bed7f283a1e3468255af49f8 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 19:12:32 +0100 Subject: [PATCH 08/13] Mention new ways to shallow-copy --- src/site/markdown/upgrading-resolver.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/site/markdown/upgrading-resolver.md b/src/site/markdown/upgrading-resolver.md index d5d2697a0..c1a1e899e 100644 --- a/src/site/markdown/upgrading-resolver.md +++ b/src/site/markdown/upgrading-resolver.md @@ -45,5 +45,6 @@ Required changes in **client code managing Resolver 2.x**: * instead, use `RepositorySystem#createSessionBuilder` to create `SessionBuilder` and out of it `CloseableRepositorySystemSession` instances. * handle sessions as resources: each created instance should be closed once finished their use. * session instances created by given `RepositorySystem` should be used only with that same instance. -* to shallow-copy session instances using existing `DefaultRepositorySystemSession` copy constructor is acceptable (this is what Mojos do). -* to shallow-copy session instances but have new lifecycle as well, use `SessionBuilder#withRepositorySystemSession`. +* to shallow-copy session instances (for alteration purposes) using existing `DefaultRepositorySystemSession` copy constructor is acceptable (this is what Mojos do). +* to shallow-copy session instances (for alteration purposes) there is `CloseableRepositorySystemSession#copy` method as well, if closeable session is needed. +* to shallow-copy session instances but have new lifecycle as well, use `SessionBuilder#withRepositorySystemSession` on newly created builder instances. From 2cce85f3c9c7132d0af04696ceec47b24f9e0d77 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 19:43:54 +0100 Subject: [PATCH 09/13] Revert the close on system --- .../org/eclipse/aether/RepositorySystem.java | 17 ++++------------- .../internal/impl/DefaultRepositorySystem.java | 5 ----- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java index 2d092d595..0cc2f2cf2 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java @@ -314,27 +314,18 @@ List newResolutionRepositories( * When shutdown happens, all the registered on-close handlers will be invoked (even if some throws), and at end * of operation a {@link MultiRuntimeException} may be thrown, signaling that some handler(s) failed. This exception * may be ignored, is at the discretion of caller. - *

- * Note: this method actually just calls {@link #close()}. * * @since 1.9.0 - * @deprecated Use {@link #close()} instead. */ - @Deprecated void shutdown(); /** - * Signals to repository system to shut down. Shut down instance is not usable anymore. - *

- * Repository system may perform some resource cleanup, if applicable. Not using this method may cause leaks or - * unclean shutdown of some subsystem. - *

- * When shutdown happens, all the registered on-close handlers will be invoked (even if some throws), and at end - * of operation a {@link MultiRuntimeException} may be thrown, signaling that some handler(s) failed. This exception - * may be ignored, is at the discretion of caller. + * Closes this instance, invokes {@link #shutdown()}. * * @since TBD */ @Override - void close(); + default void close() { + shutdown(); + } } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java index 131929bfc..8c1ffe1ef 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java @@ -406,11 +406,6 @@ public RepositorySystemSession.SessionBuilder createSessionBuilder() { @Override public void shutdown() { - close(); - } - - @Override - public void close() { if (closed.compareAndSet(false, true)) { repositorySystemLifecycle.systemEnded(); } From e749650269a5589a9df79956fb9dce05a3d676f0 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 20:39:57 +0100 Subject: [PATCH 10/13] Reformat --- .../transport/jdk/JdkHttpTransporterCustomizer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java index 8e98192d3..4dd30d8c2 100644 --- a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java +++ b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/src/main/java/org/eclipse/aether/transport/jdk/JdkHttpTransporterCustomizer.java @@ -36,6 +36,7 @@ */ final class JdkHttpTransporterCustomizer { private static final Logger LOGGER = LoggerFactory.getLogger(JdkHttpTransporterCustomizer.class); + private JdkHttpTransporterCustomizer() {} static void customizeBuilder( @@ -47,10 +48,9 @@ static void customizeBuilder( } static void customizeHttpClient(RepositorySystemSession session, RemoteRepository repository, HttpClient client) { - if (!session.addOnSessionEndedHandler(client::close)) { - LOGGER.warn( - "Using Resolver 2 feature without Resolver 2 session handling, you may leak resources."); - } + if (!session.addOnSessionEndedHandler(client::close)) { + LOGGER.warn("Using Resolver 2 feature without Resolver 2 session handling, you may leak resources."); + } } /** From e383d56fc3d7af34a03b340c06eb77ba107e65ff Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 21:37:53 +0100 Subject: [PATCH 11/13] Shutdown is real thing, close is just redirection to it. --- .../org/eclipse/aether/supplier/RepositorySystemSupplier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java b/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java index 759019b78..38f1eb6d6 100644 --- a/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java +++ b/maven-resolver-supplier/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java @@ -130,7 +130,7 @@ /** * A simple {@link Supplier} of {@link org.eclipse.aether.RepositorySystem} instances, that on each call supplies newly - * constructed instance. For proper shut down, use {@link RepositorySystem#close()} method on supplied instance(s). + * constructed instance. For proper shut down, use {@link RepositorySystem#shutdown()} method on supplied instance(s). *

* Extend this class and override methods to customize, if needed. * From 62009e82ff333c9ada66a73b6dd4307f92c66993 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 21:50:16 +0100 Subject: [PATCH 12/13] Undo some changes, add system validation --- .../internal/impl/DefaultRepositorySystem.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java index 8c1ffe1ef..3769bcc5f 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java @@ -99,7 +99,7 @@ @Singleton @Named public class DefaultRepositorySystem implements RepositorySystem { - private final AtomicBoolean closed; + private final AtomicBoolean shutdown; private final AtomicInteger sessionIdCounter; @@ -142,7 +142,7 @@ public DefaultRepositorySystem( SyncContextFactory syncContextFactory, RemoteRepositoryManager remoteRepositoryManager, RepositorySystemLifecycle repositorySystemLifecycle) { - this.closed = new AtomicBoolean(false); + this.shutdown = new AtomicBoolean(false); this.sessionIdCounter = new AtomicInteger(0); this.versionResolver = requireNonNull(versionResolver, "version resolver cannot be null"); this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null"); @@ -395,18 +395,20 @@ public RemoteRepository newDeploymentRepository(RepositorySystemSession session, @Override public void addOnSystemEndedHandler(Runnable handler) { + validateSystem(); repositorySystemLifecycle.addOnSystemEndedHandler(handler); } @Override public RepositorySystemSession.SessionBuilder createSessionBuilder() { + validateSystem(); return new DefaultSessionBuilder( this, repositorySystemLifecycle, "id-" + sessionIdCounter.incrementAndGet(), null); } @Override public void shutdown() { - if (closed.compareAndSet(false, true)) { + if (shutdown.compareAndSet(false, true)) { repositorySystemLifecycle.systemEnded(); } } @@ -422,7 +424,11 @@ private void validateSession(RepositorySystemSession session) { invalidSession(session.getAuthenticationSelector(), "authentication selector"); invalidSession(session.getArtifactTypeRegistry(), "artifact type registry"); invalidSession(session.getData(), "data"); - if (closed.get()) { + validateSystem(); + } + + private void validateSystem() { + if (shutdown.get()) { throw new IllegalStateException("repository system is already shut down"); } } From dbf846ad717527a6aac5fa0235b9291869eed893 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Wed, 8 Nov 2023 22:04:09 +0100 Subject: [PATCH 13/13] Javadoc tidy --- .../java/org/eclipse/aether/RepositorySystemSession.java | 9 ++++----- .../internal/impl/session/DefaultSessionBuilder.java | 5 +++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java index e9a3af058..2f1ce20c4 100644 --- a/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java +++ b/maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystemSession.java @@ -52,15 +52,15 @@ public interface RepositorySystemSession { /** - * Immutable session that is closeable, can have onClose handlers registered and should be handled as a resource. - * These session instances can be created with {@link SessionBuilder}. + * Immutable session that is closeable, should be handled as a resource. These session instances can be + * created with {@link SessionBuilder}. * * @since TBD */ interface CloseableRepositorySystemSession extends RepositorySystemSession, Closeable { /** * Returns the ID of this closeable session instance. Each closeable session has different ID, unique within - * repository system, that they were created for. + * repository system they were created with. * * @return The session ID that is never {@code null}. */ @@ -89,8 +89,7 @@ interface CloseableRepositorySystemSession extends RepositorySystemSession, Clos /** * Builder for building {@link CloseableRepositorySystemSession} instances. Builder instances can be created with - * {@link RepositorySystem#createSessionBuilder()} method, and built sessions must be handled as resources - * (closed once done with them). + * {@link RepositorySystem#createSessionBuilder()} method. * * @since TBD */ diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java index b58cbc96a..2077d87e0 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java @@ -53,6 +53,11 @@ /** * A default implementation of session builder. + *

+ * Note: while this class implements {@link RepositorySystemSession}, it should NOT be used as such, it is just + * an internal technical detail to allow this class some more functional helper abilities, like + * {@link #withLocalRepository(File)} method is, where "chicken or egg" situation would be present. This class is + * NOT immutable nor thread safe. */ public final class DefaultSessionBuilder implements SessionBuilder, RepositorySystemSession { private static final MirrorSelector NULL_MIRROR_SELECTOR = r -> null;