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.
*
@@ -308,4 +318,14 @@ List newResolutionRepositories(
* @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 c101621ee..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
@@ -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,362 @@
*/
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
+ * 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).
+ */
+ @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.
+ *
+ * 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 +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.
+ *
+ * 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 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..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
@@ -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;
@@ -99,6 +101,8 @@
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 DefaultRepositorySystem(
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 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 (shutdown.compareAndSet(false, true)) {
@@ -411,6 +424,10 @@ private void validateSession(RepositorySystemSession session) {
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 0d462aa8c..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,10 +23,13 @@
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 onSystemEndedHandlers;
+ private final ConcurrentHashMap> onSessionEndedHandlers;
+
@Inject
public DefaultRepositorySystemLifecycle() {
this.shutdown = new AtomicBoolean(false);
this.onSystemEndedHandlers = new CopyOnWriteArrayList<>();
+ this.onSessionEndedHandlers = new ConcurrentHashMap<>();
}
@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();
@@ -69,6 +88,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..a9bc520da
--- /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 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 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 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,
+ 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 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..2077d87e0
--- /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.
+ *
+ * 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 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,
+ 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 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,
+ 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 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-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-19/pom.xml b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-19/pom.xml
index 35d97f29b..423aff856 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 @@
+
+ org.slf4j
+ slf4j-api
+
org.apache.maven.resolver
maven-resolver-api
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 9e0b6dad9..f71b38e2f 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 @@
+
+ org.slf4j
+ slf4j-api
+
org.apache.maven.resolver
maven-resolver-api
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 51b8b9f3d..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
@@ -26,6 +26,8 @@
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 @@
* @since TBD
*/
final class JdkHttpTransporterCustomizer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JdkHttpTransporterCustomizer.class);
+
private JdkHttpTransporterCustomizer() {}
static void customizeBuilder(
@@ -44,7 +48,9 @@ static void customizeBuilder(
}
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 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 b90c1cb93..3d66c1636 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 @@
+
+ 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) {
diff --git a/src/site/markdown/upgrading-resolver.md b/src/site/markdown/upgrading-resolver.md
index 454bbbefb..c1a1e899e 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.