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

@@ -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.