This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/master by this push:
new a6faf6d8 [MRESOLVER-302] Introduce onSessionClose (#357)
a6faf6d8 is described below
commit a6faf6d86f1270b36be4429f97f59310abfff9a6
Author: Tamas Cservenak <[email protected]>
AuthorDate: Thu Nov 9 11:22:20 2023 +0100
[MRESOLVER-302] Introduce onSessionClose (#357)
And make it used in new HTTP/2 transports.
Note about implementation:
* code base integrating Resolver 2.x (like Maven) SHOULD switch to new
session handling
* code base using resolver w/o managing it (like Maven Mojos are) does NOT
have to move to new session handling (basically resolver for them work
unchanged).
In short, the Resolver 1.x session handling is still present, but is
deprecated (DefaultRepositorySystemSession default ctor) and will emit warnings
if application integrating Resolver 2.x still uses Resolver 1.x session
handling and is about to use new Resolver 2.x features (like HTTP/2 transport)
that require onSessionClose. Doing that will produce leaks, unless they are
one-time CLI apps.
---
https://issues.apache.org/jira/browse/MRESOLVER-302
---
.../AbstractForwardingRepositorySystemSession.java | 5 +
.../aether/DefaultRepositorySystemSession.java | 24 +-
.../java/org/eclipse/aether/RepositorySystem.java | 24 +-
.../eclipse/aether/RepositorySystemSession.java | 372 +++++++++++++
.../aether/impl/RepositorySystemLifecycle.java | 31 ++
.../internal/impl/DefaultRepositorySystem.java | 17 +
.../impl/DefaultRepositorySystemLifecycle.java | 74 ++-
.../DefaultCloseableRepositorySystemSession.java | 343 ++++++++++++
.../impl/session/DefaultSessionBuilder.java | 580 +++++++++++++++++++++
.../maven-resolver-transport-jdk-11/pom.xml | 4 +
.../maven-resolver-transport-jdk-19/pom.xml | 4 +
.../maven-resolver-transport-jdk-21/pom.xml | 4 +
.../jdk/JdkHttpTransporterCustomizer.java | 8 +-
.../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 +
.../aether/transport/jetty/JettyTransporter.java | 14 +
src/site/markdown/upgrading-resolver.md | 24 +
19 files changed, 1539 insertions(+), 6 deletions(-)
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 5e3a60d4..9b7652f6 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 abstract class
AbstractForwardingRepositorySystemSession implements Repos
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 3f46af54..5ad90103 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 @@ package org.eclipse.aether;
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 @@ import static java.util.Objects.requireNonNull;
* <strong>Note:</strong> 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<Runnable, Boolean> onSessionEndedRegistrar;
+
/**
* Creates an uninitialized session. <em>Note:</em> 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 final class DefaultRepositorySystemSession
implements RepositorySystemSes
authenticationSelector = NullAuthenticationSelector.INSTANCE;
artifactTypeRegistry = NullArtifactTypeRegistry.INSTANCE;
data = new DefaultSessionData();
+ onSessionEndedRegistrar = h -> false;
}
/**
@@ -168,6 +176,7 @@ public final class DefaultRepositorySystemSession
implements RepositorySystemSes
setDependencyGraphTransformer(session.getDependencyGraphTransformer());
setData(session.getData());
setCache(session.getCache());
+ this.onSessionEndedRegistrar = session::addOnSessionEndedHandler;
}
@Override
@@ -766,6 +775,17 @@ public final class DefaultRepositorySystemSession
implements RepositorySystemSes
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 7253c209..0cc2f2cf 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 @@ import org.eclipse.aether.resolution.VersionResult;
* @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 @@ public interface RepositorySystem {
* {@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 @@ public interface RepositorySystem {
*/
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.
* <p>
@@ -308,4 +318,14 @@ public interface RepositorySystem {
* @since 1.9.0
*/
void shutdown();
+
+ /**
+ * Closes this instance, invokes {@link #shutdown()}.
+ *
+ * @since TBD
+ */
+ @Override
+ default void close() {
+ shutdown();
+ }
}
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 c101621e..2f1ce20c 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.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.RepositoryPolicy;
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.resolution.ArtifactDescriptorPolicy;
@@ -48,6 +51,362 @@ import org.eclipse.aether.transfer.TransferListener;
*/
public interface RepositorySystemSession {
+ /**
+ * 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 they were created with.
+ *
+ * @return The session ID that is never {@code null}.
+ */
+ String sessionId();
+
+ /**
+ * Copies this session into a pre-populated builder, effectively
making a mutable copy of itself, builder builds
+ * <em>same session</em>. Important: this session <em>remains
unchanged</em> upon return of this method but
+ * this session and returned builder created session will have
<em>same identity</em>. 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.
+ * <p>
+ * 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).
+ */
+ @Override
+ void close();
+ }
+
+ /**
+ * Builder for building {@link CloseableRepositorySystemSession}
instances. Builder instances can be created with
+ * {@link RepositorySystem#createSessionBuilder()} method.
+ *
+ * @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.
+ * <p>
+ * 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 <em>should
not mix the two!</em>
+ *
+ * @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.
<em>Note:</em> 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.
+ * <p>
+ * <em>Note:</em> System properties are of type {@code Map<String,
String>} 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.
+ * <p>
+ * <em>Note:</em> User properties are of type {@code Map<String,
String>} 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.).
+ * <p>
+ * <em>Note:</em> Configuration properties are of type {@code
Map<String, Object>} 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 +642,17 @@ public interface RepositorySystemSession {
* @return The repository cache or {@code null} if none.
*/
RepositoryCache getCache();
+
+ /**
+ * Registers a handler to execute when this session closed.
+ * <p>
+ * 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 successfully registered, {@code false}
otherwise.
+ * @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 b3a0c44e..3048ed7e 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.
+ * <p>
+ * <em>Same session instance can be started only once.</em>
+ *
+ * @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.
+ * <p>
+ * <em>Same session instance can be ended only once.</em>
+ *
+ * @since TBD
+ */
+ void sessionEnded(CloseableRepositorySystemSession session);
+
+ /**
+ * Registers an "on session end" handler.
+ * <p>
+ * 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 19c89723..3769bcc5 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.Collection;
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.impl.VersionResolver;
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;
@@ -99,6 +101,8 @@ import static java.util.Objects.requireNonNull;
public class DefaultRepositorySystem implements RepositorySystem {
private final AtomicBoolean shutdown;
+ private final AtomicInteger sessionIdCounter;
+
private final VersionResolver versionResolver;
private final VersionRangeResolver versionRangeResolver;
@@ -139,6 +143,7 @@ public class DefaultRepositorySystem implements
RepositorySystem {
RemoteRepositoryManager remoteRepositoryManager,
RepositorySystemLifecycle repositorySystemLifecycle) {
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");
this.artifactResolver = requireNonNull(artifactResolver, "artifact
resolver cannot be null");
@@ -390,9 +395,17 @@ public class DefaultRepositorySystem implements
RepositorySystem {
@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 (shutdown.compareAndSet(false, true)) {
@@ -411,6 +424,10 @@ public class DefaultRepositorySystem implements
RepositorySystem {
invalidSession(session.getAuthenticationSelector(), "authentication
selector");
invalidSession(session.getArtifactTypeRegistry(), "artifact type
registry");
invalidSession(session.getData(), "data");
+ validateSystem();
+ }
+
+ private void validateSystem() {
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/DefaultRepositorySystemLifecycle.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemLifecycle.java
index 0d462aa8..73a25c7a 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,13 @@ import javax.inject.Named;
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;
import org.eclipse.aether.MultiRuntimeException;
+import
org.eclipse.aether.RepositorySystemSession.CloseableRepositorySystemSession;
import org.eclipse.aether.impl.RepositorySystemLifecycle;
import static java.util.Objects.requireNonNull;
@@ -41,16 +44,32 @@ public class DefaultRepositorySystemLifecycle implements
RepositorySystemLifecyc
private final CopyOnWriteArrayList<Runnable> onSystemEndedHandlers;
+ private final ConcurrentHashMap<String, CopyOnWriteArrayList<Runnable>>
onSessionEndedHandlers;
+
@Inject
public DefaultRepositorySystemLifecycle() {
this.shutdown = new AtomicBoolean(false);
this.onSystemEndedHandlers = new CopyOnWriteArrayList<>();
+ this.onSessionEndedHandlers = new ConcurrentHashMap<>();
}
@Override
public void systemEnded() {
+ final ArrayList<Exception> exceptions = new ArrayList<>();
if (shutdown.compareAndSet(false, true)) {
- final ArrayList<Exception> exceptions = new ArrayList<>();
+ for (Map.Entry<String, CopyOnWriteArrayList<Runnable>>
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();
@@ -69,6 +88,59 @@ public class DefaultRepositorySystemLifecycle implements
RepositorySystemLifecyc
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<Runnable> handlers = new ArrayList<>();
+ onSessionEndedHandlers.compute(sessionId, (k, v) -> {
+ if (v == null) {
+ throw new IllegalStateException("session instance not
registered");
+ }
+ handlers.addAll(v);
+ return null;
+ });
+
+ ArrayList<Exception> 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 00000000..a9bc520d
--- /dev/null
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultCloseableRepositorySystemSession.java
@@ -0,0 +1,343 @@
+/*
+ * 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.RepositorySystem;
+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 String sessionId;
+
+ private final AtomicBoolean closed;
+
+ 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<String, String> systemProperties;
+
+ private final Map<String, String> userProperties;
+
+ private final Map<String, Object> 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 RepositorySystem repositorySystem;
+
+ private final RepositorySystemLifecycle repositorySystemLifecycle;
+
+ @SuppressWarnings("checkstyle:parameternumber")
+ public DefaultCloseableRepositorySystemSession(
+ String sessionId,
+ AtomicBoolean closed,
+ boolean offline,
+ boolean ignoreArtifactDescriptorRepositories,
+ ResolutionErrorPolicy resolutionErrorPolicy,
+ ArtifactDescriptorPolicy artifactDescriptorPolicy,
+ String checksumPolicy,
+ String artifactUpdatePolicy,
+ String metadataUpdatePolicy,
+ LocalRepositoryManager localRepositoryManager,
+ WorkspaceReader workspaceReader,
+ RepositoryListener repositoryListener,
+ TransferListener transferListener,
+ Map<String, String> systemProperties,
+ Map<String, String> userProperties,
+ Map<String, Object> configProperties,
+ MirrorSelector mirrorSelector,
+ ProxySelector proxySelector,
+ AuthenticationSelector authenticationSelector,
+ ArtifactTypeRegistry artifactTypeRegistry,
+ DependencyTraverser dependencyTraverser,
+ DependencyManager dependencyManager,
+ DependencySelector dependencySelector,
+ VersionFilter versionFilter,
+ DependencyGraphTransformer dependencyGraphTransformer,
+ SessionData data,
+ RepositoryCache cache,
+ RepositorySystem repositorySystem,
+ RepositorySystemLifecycle repositorySystemLifecycle) {
+ this.sessionId = requireNonNull(sessionId);
+ this.closed = closed == null ? new AtomicBoolean(false) : closed;
+ 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.repositorySystem = requireNonNull(repositorySystem);
+ this.repositorySystemLifecycle =
requireNonNull(repositorySystemLifecycle);
+
+ if (closed == null) {
+ repositorySystemLifecycle.sessionStarted(this);
+ }
+ }
+
+ @Override
+ public String sessionId() {
+ return sessionId;
+ }
+
+ @Override
+ public SessionBuilder copy() {
+ return new DefaultSessionBuilder(repositorySystem,
repositorySystemLifecycle, sessionId, closed)
+ .withRepositorySystemSession(this);
+ }
+
+ @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<String, String> getSystemProperties() {
+ return systemProperties;
+ }
+
+ @Override
+ public Map<String, String> getUserProperties() {
+ return userProperties;
+ }
+
+ @Override
+ public Map<String, Object> 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 00000000..2077d87e
--- /dev/null
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/session/DefaultSessionBuilder.java
@@ -0,0 +1,580 @@
+/*
+ * 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 java.util.concurrent.atomic.AtomicBoolean;
+
+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.
+ * <p>
+ * 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;
+
+ 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 AtomicBoolean closed;
+
+ private final ArrayList<Runnable> 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<String, String> systemProperties = new HashMap<>();
+
+ private Map<String, String> userProperties = new HashMap<>();
+
+ private Map<String, Object> 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,
+ AtomicBoolean closed) {
+ this.repositorySystem = requireNonNull(repositorySystem);
+ this.repositorySystemLifecycle =
requireNonNull(repositorySystemLifecycle);
+ this.sessionId = requireNonNull(sessionId);
+ this.closed = closed;
+ 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<String, String> getSystemProperties() {
+ return systemProperties;
+ }
+
+ @Override
+ public Map<String, String> getUserProperties() {
+ return userProperties;
+ }
+
+ @Override
+ public Map<String, Object> 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,
+ closed,
+ 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,
+ repositorySystem,
+ repositorySystemLifecycle);
+ onCloseHandler.forEach(result::addOnSessionEndedHandler);
+ return result;
+ }
+
+ @SuppressWarnings("checkstyle:magicnumber")
+ private static <T> Map<String, T> copySafe(Map<?, ?> table, Class<T>
valueType) {
+ Map<String, T> 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-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/pom.xml
index b8f544d8..49e2bcf4 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 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
diff --git
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
index 35d97f29..423aff85 100644
---
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
+++
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
@@ -43,6 +43,10 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
diff --git
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml
index 9e0b6dad..f71b38e2 100644
---
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml
+++
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-21/pom.xml
@@ -43,6 +43,10 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
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 51b8b9f3..4dd30d8c 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
@@ -26,6 +26,8 @@ import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.ConfigUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* JDK Transport customizer.
@@ -33,6 +35,8 @@ import org.eclipse.aether.util.ConfigUtils;
* @since TBD
*/
final class JdkHttpTransporterCustomizer {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(JdkHttpTransporterCustomizer.class);
+
private JdkHttpTransporterCustomizer() {}
static void customizeBuilder(
@@ -44,7 +48,9 @@ final class JdkHttpTransporterCustomizer {
}
static void customizeHttpClient(RepositorySystemSession session,
RemoteRepository repository, HttpClient client) {
- // TODO: register client.close(); once onSessionClose feature present
+ if (!session.addOnSessionEndedHandler(client::close)) {
+ LOGGER.warn("Using Resolver 2 feature without Resolver 2 session
handling, you may leak resources.");
+ }
}
/**
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 ff084748..ab221e91 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 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
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 80dd1d65..81b8c34f 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.repository.RemoteRepository;
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 @@ import static java.util.Objects.requireNonNull;
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 final class JdkTransporterFactory implements
TransporterFactory {
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 b90c1cb9..3d66c163 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
@@ -65,6 +65,10 @@
</dependency>
<!-- Real dependencies -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
diff --git a/maven-resolver-transport-jetty/pom.xml
b/maven-resolver-transport-jetty/pom.xml
index e9c2b630..3243d082 100644
--- a/maven-resolver-transport-jetty/pom.xml
+++ b/maven-resolver-transport-jetty/pom.xml
@@ -41,6 +41,10 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
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 56f8d93e..de71e68d 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.HTTP2Client;
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 @@ final class JettyTransporter extends AbstractTransporter {
*/
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 @@ final class JettyTransporter extends AbstractTransporter {
}
}
}
+ 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) {
diff --git a/src/site/markdown/upgrading-resolver.md
b/src/site/markdown/upgrading-resolver.md
index 454bbbef..c1a1e899 100644
--- a/src/site/markdown/upgrading-resolver.md
+++ b/src/site/markdown/upgrading-resolver.md
@@ -24,3 +24,27 @@ 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 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) 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).
+
+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
constructor 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 (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.