This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new b1ed809b [MRESOLVER-278] Session close and onClose hooks (#201)
b1ed809b is described below

commit b1ed809bc7f9ebc49d2fefd3e5a9f6a8ce4fa11a
Author: Tamas Cservenak <[email protected]>
AuthorDate: Fri Oct 14 22:08:24 2022 +0200

    [MRESOLVER-278] Session close and onClose hooks (#201)
    
    Make session extend `AutoCloseable`, denoting that given
    session instance is "done". This is integrator app
    obligation to close the "done" session.
    
    Also introdue onClose callbacks, that various components
    may use to hook when session is marked "done" to
    perform various cleanups.
    
    ---
    
    https://issues.apache.org/jira/browse/MRESOLVER-278
---
 .../AbstractForwardingRepositorySystemSession.java |  24 +++++
 .../aether/DefaultRepositorySystemSession.java     | 110 +++++++++++++++------
 .../org/eclipse/aether/MultiRuntimeException.java  |  74 ++++++++++++++
 .../eclipse/aether/RepositorySystemSession.java    |  56 ++++++++++-
 .../aether/DefaultRepositorySystemSessionTest.java |   5 +-
 .../sisu/SisuRepositorySystemDemoModule.java       |   2 -
 maven-resolver-impl/pom.xml                        |   5 -
 .../internal/impl/DefaultRepositorySystem.java     |   4 +
 .../SummaryFileTrustedChecksumsSource.java         |  73 +++++++++++---
 .../synccontext/DefaultSyncContextFactory.java     |  71 +++++--------
 10 files changed, 325 insertions(+), 99 deletions(-)

diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/AbstractForwardingRepositorySystemSession.java
index 008a2df5..8fe24c1a 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
@@ -20,6 +20,7 @@ package org.eclipse.aether;
  */
 
 import java.util.Map;
+import java.util.function.Consumer;
 
 import org.eclipse.aether.artifact.ArtifactTypeRegistry;
 import org.eclipse.aether.collection.DependencyGraphTransformer;
@@ -218,4 +219,27 @@ public abstract class 
AbstractForwardingRepositorySystemSession
         return getSession().getFileTransformerManager();
     }
 
+    @Override
+    public final void addOnCloseHandler( Consumer<RepositorySystemSession> 
handler )
+    {
+        getSession().addOnCloseHandler( handler );
+    }
+
+    @Override
+    public final boolean isClosed()
+    {
+        return getSession().isClosed();
+    }
+
+    /**
+     * This method is special: by default it throws (nested session should 
never be closed), the "top level" session
+     * should be closed instead. Still, this method is NOT {@code final}, to 
allow implementations overriding it,
+     * and in case when needed, handle forwarded session as "top level" 
session.
+     */
+    @Override
+    public void close()
+    {
+        throw new IllegalStateException( "Forwarding session should not be 
closed, "
+                + "close the top-level (forwarded) session instead." );
+    }
 }
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 90b32f48..4450487f 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
@@ -19,12 +19,16 @@ package org.eclipse.aether;
  * under the License.
  */
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import static java.util.Objects.requireNonNull;
 
 import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 import org.eclipse.aether.artifact.Artifact;
 import org.eclipse.aether.artifact.ArtifactType;
@@ -62,6 +66,8 @@ public final class DefaultRepositorySystemSession
     implements RepositorySystemSession
 {
 
+    private final AtomicBoolean closed;
+
     private boolean readOnly;
 
     private boolean offline;
@@ -127,6 +133,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession()
     {
+        closed = new AtomicBoolean( false );
         systemProperties = new HashMap<>();
         systemPropertiesView = Collections.unmodifiableMap( systemProperties );
         userProperties = new HashMap<>();
@@ -153,6 +160,7 @@ public final class DefaultRepositorySystemSession
     {
         requireNonNull( session, "repository system session cannot be null" );
 
+        closed = new AtomicBoolean( false );
         setOffline( session.isOffline() );
         setIgnoreArtifactDescriptorRepositories( 
session.isIgnoreArtifactDescriptorRepositories() );
         setResolutionErrorPolicy( session.getResolutionErrorPolicy() );
@@ -194,7 +202,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setOffline( boolean offline )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.offline = offline;
         return this;
     }
@@ -215,7 +223,7 @@ public final class DefaultRepositorySystemSession
     public DefaultRepositorySystemSession 
setIgnoreArtifactDescriptorRepositories(
             boolean ignoreArtifactDescriptorRepositories )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.ignoreArtifactDescriptorRepositories = 
ignoreArtifactDescriptorRepositories;
         return this;
     }
@@ -234,7 +242,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setResolutionErrorPolicy( 
ResolutionErrorPolicy resolutionErrorPolicy )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.resolutionErrorPolicy = resolutionErrorPolicy;
         return this;
     }
@@ -254,7 +262,7 @@ public final class DefaultRepositorySystemSession
     public DefaultRepositorySystemSession setArtifactDescriptorPolicy(
             ArtifactDescriptorPolicy artifactDescriptorPolicy )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.artifactDescriptorPolicy = artifactDescriptorPolicy;
         return this;
     }
@@ -276,7 +284,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setChecksumPolicy( String 
checksumPolicy )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.checksumPolicy = checksumPolicy;
         return this;
     }
@@ -298,7 +306,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setUpdatePolicy( String updatePolicy 
)
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.updatePolicy = updatePolicy;
         return this;
     }
@@ -323,7 +331,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setLocalRepositoryManager( 
LocalRepositoryManager localRepositoryManager )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.localRepositoryManager = localRepositoryManager;
         return this;
     }
@@ -336,7 +344,7 @@ public final class DefaultRepositorySystemSession
 
     public DefaultRepositorySystemSession setFileTransformerManager( 
FileTransformerManager fileTransformerManager )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.fileTransformerManager = fileTransformerManager;
         if ( this.fileTransformerManager == null )
         {
@@ -359,7 +367,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setWorkspaceReader( WorkspaceReader 
workspaceReader )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.workspaceReader = workspaceReader;
         return this;
     }
@@ -377,7 +385,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setRepositoryListener( 
RepositoryListener repositoryListener )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.repositoryListener = repositoryListener;
         return this;
     }
@@ -395,7 +403,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setTransferListener( 
TransferListener transferListener )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.transferListener = transferListener;
         return this;
     }
@@ -444,7 +452,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setSystemProperties( Map<?, ?> 
systemProperties )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.systemProperties = copySafe( systemProperties, String.class );
         systemPropertiesView = Collections.unmodifiableMap( 
this.systemProperties );
         return this;
@@ -459,7 +467,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setSystemProperty( String key, 
String value )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         if ( value != null )
         {
             systemProperties.put( key, value );
@@ -489,7 +497,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setUserProperties( Map<?, ?> 
userProperties )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.userProperties = copySafe( userProperties, String.class );
         userPropertiesView = Collections.unmodifiableMap( this.userProperties 
);
         return this;
@@ -504,7 +512,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setUserProperty( String key, String 
value )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         if ( value != null )
         {
             userProperties.put( key, value );
@@ -533,7 +541,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setConfigProperties( Map<?, ?> 
configProperties )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.configProperties = copySafe( configProperties, Object.class );
         configPropertiesView = Collections.unmodifiableMap( 
this.configProperties );
         return this;
@@ -548,7 +556,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setConfigProperty( String key, 
Object value )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         if ( value != null )
         {
             configProperties.put( key, value );
@@ -575,7 +583,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setMirrorSelector( MirrorSelector 
mirrorSelector )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.mirrorSelector = mirrorSelector;
         if ( this.mirrorSelector == null )
         {
@@ -600,7 +608,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setProxySelector( ProxySelector 
proxySelector )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.proxySelector = proxySelector;
         if ( this.proxySelector == null )
         {
@@ -625,7 +633,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setAuthenticationSelector( 
AuthenticationSelector authenticationSelector )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.authenticationSelector = authenticationSelector;
         if ( this.authenticationSelector == null )
         {
@@ -647,7 +655,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setArtifactTypeRegistry( 
ArtifactTypeRegistry artifactTypeRegistry )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.artifactTypeRegistry = artifactTypeRegistry;
         if ( this.artifactTypeRegistry == null )
         {
@@ -669,7 +677,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setDependencyTraverser( 
DependencyTraverser dependencyTraverser )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.dependencyTraverser = dependencyTraverser;
         return this;
     }
@@ -687,7 +695,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setDependencyManager( 
DependencyManager dependencyManager )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.dependencyManager = dependencyManager;
         return this;
     }
@@ -705,7 +713,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setDependencySelector( 
DependencySelector dependencySelector )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.dependencySelector = dependencySelector;
         return this;
     }
@@ -724,7 +732,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setVersionFilter( VersionFilter 
versionFilter )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.versionFilter = versionFilter;
         return this;
     }
@@ -744,7 +752,7 @@ public final class DefaultRepositorySystemSession
     public DefaultRepositorySystemSession setDependencyGraphTransformer(
             DependencyGraphTransformer dependencyGraphTransformer )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.dependencyGraphTransformer = dependencyGraphTransformer;
         return this;
     }
@@ -762,7 +770,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setData( SessionData data )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.data = data;
         if ( this.data == null )
         {
@@ -784,7 +792,7 @@ public final class DefaultRepositorySystemSession
      */
     public DefaultRepositorySystemSession setCache( RepositoryCache cache )
     {
-        failIfReadOnly();
+        verifyStateForMutation();
         this.cache = cache;
         return this;
     }
@@ -799,12 +807,19 @@ public final class DefaultRepositorySystemSession
         readOnly = true;
     }
 
-    private void failIfReadOnly()
+    /**
+     * Verifies this instance state for mutation operations: mutated instance 
must not be read-only or closed.
+     */
+    private void verifyStateForMutation()
     {
         if ( readOnly )
         {
             throw new IllegalStateException( "repository system session is 
read-only" );
         }
+        if ( isClosed() )
+        {
+            throw new IllegalStateException( "repository system session is 
closed" );
+        }
     }
 
     static class NullProxySelector
@@ -873,4 +888,41 @@ public final class DefaultRepositorySystemSession
         }
     }
 
+    private final CopyOnWriteArrayList<Consumer<RepositorySystemSession>> 
onCloseHandlers
+            = new CopyOnWriteArrayList<>();
+
+    @Override
+    public void addOnCloseHandler( Consumer<RepositorySystemSession> handler )
+    {
+        verifyStateForMutation();
+        requireNonNull( handler, "handler cannot be null" );
+        onCloseHandlers.add( 0, handler );
+    }
+
+    @Override
+    public boolean isClosed()
+    {
+        return closed.get();
+    }
+
+    @Override
+    public void close()
+    {
+        if ( closed.compareAndSet( false, true ) )
+        {
+            ArrayList<Exception> exceptions = new ArrayList<>();
+            for ( Consumer<RepositorySystemSession> onCloseHandler : 
onCloseHandlers )
+            {
+                try
+                {
+                    onCloseHandler.accept( this );
+                }
+                catch ( Exception e )
+                {
+                    exceptions.add( e );
+                }
+            }
+            MultiRuntimeException.mayThrow( "session on-close handler 
failures", exceptions );
+        }
+    }
 }
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/MultiRuntimeException.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/MultiRuntimeException.java
new file mode 100644
index 00000000..24d48f93
--- /dev/null
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/MultiRuntimeException.java
@@ -0,0 +1,74 @@
+package org.eclipse.aether;
+
+/*
+ * 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.
+ */
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Runtime exception to be thrown when multiple actions were executed and one 
or more failed. To be used when no
+ * fallback on resolver side is needed or is possible.
+ *
+ * @since TBD
+ */
+public final class MultiRuntimeException
+        extends RuntimeException
+{
+    private final List<? extends Throwable> throwables;
+
+    private MultiRuntimeException( String message, List<? extends Throwable> 
throwables )
+    {
+        super( message );
+        this.throwables = throwables;
+        for ( Throwable throwable : throwables )
+        {
+            addSuppressed( throwable );
+        }
+    }
+
+    /**
+     * Returns the list of throwables that are wrapped in this exception.
+     *
+     * @return The list of throwables, never {@code null}.
+     */
+    public List<? extends Throwable> getThrowables()
+    {
+        return throwables;
+    }
+
+    /**
+     * Helper method that receives a (non-null) message and (non-null) list of 
throwable, and following happens:
+     * <ul>
+     *     <li>if list is empty - nothing</li>
+     *     <li>if list not empty - {@link MultiRuntimeException} is thrown 
wrapping all elements</li>
+     * </ul>
+     */
+    public static void mayThrow( String message, List<? extends Throwable> 
throwables )
+    {
+        requireNonNull( message );
+        requireNonNull( throwables );
+
+        if ( !throwables.isEmpty() )
+        {
+            throw new MultiRuntimeException( message, throwables );
+        }
+    }
+}
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 7aef49af..0c2bbac9 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
@@ -20,6 +20,7 @@ package org.eclipse.aether;
  */
 
 import java.util.Map;
+import java.util.function.Consumer;
 
 import org.eclipse.aether.artifact.ArtifactTypeRegistry;
 import org.eclipse.aether.collection.DependencyGraphTransformer;
@@ -48,7 +49,7 @@ import org.eclipse.aether.transform.FileTransformerManager;
  * @noimplement This interface is not intended to be implemented by clients.
  * @noextend This interface is not intended to be extended by clients.
  */
-public interface RepositorySystemSession
+public interface RepositorySystemSession extends AutoCloseable
 {
 
     /**
@@ -271,4 +272,57 @@ public interface RepositorySystemSession
     @Deprecated
     FileTransformerManager getFileTransformerManager();
 
+    /**
+     * Adds "on close" handler to this session, it must not be {@code null}. 
Note: when handlers are invoked, the
+     * passed in (this) session is ALREADY CLOSED (the {@link #isClosed()} 
method returns {@code true}). This implies,
+     * that handlers cannot use {@link RepositorySystem} to 
resolve/collect/and so on, handlers are meant to perform
+     * some internal cleanup on session close. Attempt to add handler to 
closed session will throw.
+     *
+     * @since TBD
+     */
+    void addOnCloseHandler( Consumer<RepositorySystemSession> handler );
+
+    /**
+     * Returns {@code true} if this instance was already closed. Closed 
sessions should NOT be used anymore.
+     *
+     * @return {@code true} if session was closed.
+     * @since TBD
+     */
+    boolean isClosed();
+
+    /**
+     * Closes this session and possibly releases related resources by invoking 
"on close" handlers added by
+     * {@link #addOnCloseHandler(Consumer<RepositorySystemSession>)} method. 
This method may be invoked multiple times,
+     * but only first invocation will actually invoke handlers, subsequent 
invocations will be no-op.
+     * <p>
+     * When close action happens, all the registered handlers will be invoked 
(even if some throws), and at end
+     * of operation a {@link MultiRuntimeException} may be thrown signaling 
that some handler(s) failed. This exception
+     * may be ignored, is at the discretion of caller.
+     * <p>
+     * Important: ideally it is the session creator who should be responsible 
to close the session. The "nested"
+     * (delegating, wrapped) sessions {@link 
AbstractForwardingRepositorySystemSession} and alike) by default
+     * (without overriding the {@link 
AbstractForwardingRepositorySystemSession#close()} method) are prevented to 
close
+     * session, and it is the "recommended" behaviour as well. On the other 
hand, "nested" session may receive new
+     * "on close" handler registrations, but those registrations are passed to 
delegated session, and will be invoked
+     * once the "top level" (delegated) session is closed.
+     * <p>
+     * New session "copy" instances created using copy-constructor
+     * {@link 
DefaultRepositorySystemSession#DefaultRepositorySystemSession(RepositorySystemSession)}
 result in new,
+     * independent session instances, and they do NOT share states like 
"read-only", "closed" and "on close handlers"
+     * with the copied session. Hence, they should be treated as "top level" 
sessions as well.
+     * <p>
+     * The recommended pattern for "top level" sessions is the usual 
try-with-resource:
+     *
+     * <pre> {@code
+     * try ( RepositorySystemSession session = create session... )
+     * {
+     *   ... use/nest session
+     * }
+     * }</pre>
+     *
+     * @since TBD
+     */
+    @Override
+    void close();
+
 }
diff --git 
a/maven-resolver-api/src/test/java/org/eclipse/aether/DefaultRepositorySystemSessionTest.java
 
b/maven-resolver-api/src/test/java/org/eclipse/aether/DefaultRepositorySystemSessionTest.java
index ccca26ad..1fe08464 100644
--- 
a/maven-resolver-api/src/test/java/org/eclipse/aether/DefaultRepositorySystemSessionTest.java
+++ 
b/maven-resolver-api/src/test/java/org/eclipse/aether/DefaultRepositorySystemSessionTest.java
@@ -140,7 +140,10 @@ public class DefaultRepositorySystemSessionTest
 
         for ( Method method : methods )
         {
-            assertEquals( method.getName(), method.invoke( session ) == null, 
method.invoke( newSession ) == null );
+            if ( method.getParameterCount() == 0 )
+            {
+                assertEquals( method.getName(), method.invoke( session ) == 
null, method.invoke( newSession ) == null );
+            }
         }
     }
 
diff --git 
a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/sisu/SisuRepositorySystemDemoModule.java
 
b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/sisu/SisuRepositorySystemDemoModule.java
index 71adb55b..d4a43c8f 100644
--- 
a/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/sisu/SisuRepositorySystemDemoModule.java
+++ 
b/maven-resolver-demos/maven-resolver-demo-snippets/src/main/java/org/apache/maven/resolver/examples/sisu/SisuRepositorySystemDemoModule.java
@@ -23,7 +23,6 @@ import javax.inject.Inject;
 
 import com.google.inject.Binder;
 import com.google.inject.Module;
-import org.eclipse.sisu.bean.LifecycleModule;
 import org.eclipse.sisu.inject.MutableBeanLocator;
 import org.eclipse.sisu.wire.ParameterKeys;
 
@@ -35,7 +34,6 @@ public class SisuRepositorySystemDemoModule implements Module
     @Override
     public void configure( final Binder binder )
     {
-        binder.install( new LifecycleModule() );
         // NOTE: Maven 3.8.1 used in demo has Sisu Index for ALL components 
(older Maven does NOT have!)
         binder.bind( ParameterKeys.PROPERTIES ).toInstance( 
System.getProperties() );
         binder.bind( ShutdownThread.class ).asEagerSingleton();
diff --git a/maven-resolver-impl/pom.xml b/maven-resolver-impl/pom.xml
index 70d0bc66..0f9fe22f 100644
--- a/maven-resolver-impl/pom.xml
+++ b/maven-resolver-impl/pom.xml
@@ -68,11 +68,6 @@
       <scope>provided</scope>
       <optional>true</optional>
     </dependency>
-    <dependency>
-      <groupId>javax.annotation</groupId>
-      <artifactId>javax.annotation-api</artifactId>
-      <version>1.3.2</version>
-    </dependency>
     <dependency>
       <groupId>org.eclipse.sisu</groupId>
       <artifactId>org.eclipse.sisu.inject</artifactId>
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 f079cff6..1f7a1c2e 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
@@ -466,6 +466,10 @@ public class DefaultRepositorySystem
         invalidSession( session.getAuthenticationSelector(), "authentication 
selector" );
         invalidSession( session.getArtifactTypeRegistry(), "artifact type 
registry" );
         invalidSession( session.getData(), "data" );
+        if ( session.isClosed() )
+        {
+            throw new IllegalStateException( "session is already closed" );
+        }
     }
 
     private void validateRepositories( List<RemoteRepository> repositories )
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java
index d6f7e21c..4568723b 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java
@@ -29,13 +29,17 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.eclipse.aether.MultiRuntimeException;
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.artifact.Artifact;
 import org.eclipse.aether.repository.ArtifactRepository;
@@ -74,6 +78,8 @@ public final class SummaryFileTrustedChecksumsSource
 
     private static final String CHECKSUMS_CACHE_KEY = 
SummaryFileTrustedChecksumsSource.class.getName() + ".checksums";
 
+    private static final String RECORDING_KEY = 
SummaryFileTrustedChecksumsSource.class.getName() + ".recording";
+
     private static final Logger LOGGER = LoggerFactory.getLogger( 
SummaryFileTrustedChecksumsSource.class );
 
     public SummaryFileTrustedChecksumsSource()
@@ -111,10 +117,17 @@ public final class SummaryFileTrustedChecksumsSource
         return checksums;
     }
 
+    @SuppressWarnings( "unchecked" )
     @Override
     protected SummaryFileWriter getWriter( RepositorySystemSession session, 
Path basedir )
     {
-        return new SummaryFileWriter( basedir, isOriginAware( session ) );
+        ConcurrentHashMap<Path, Set<String>> recordedLines = 
(ConcurrentHashMap<Path, Set<String>>) session.getData()
+                .computeIfAbsent( RECORDING_KEY, () ->
+                {
+                    session.addOnCloseHandler( this::saveSessionRecordedLines 
);
+                    return new ConcurrentHashMap<>();
+                } );
+        return new SummaryFileWriter( basedir, isOriginAware( session ), 
recordedLines );
     }
 
     private ConcurrentHashMap<String, String> loadProvidedChecksums( Path 
basedir,
@@ -201,41 +214,38 @@ public final class SummaryFileTrustedChecksumsSource
         return fileName + "." + checksumAlgorithmFactory.getFileExtension();
     }
 
-    /**
-     * Note: this implementation will work only in single-thread (T1) model. 
While not ideal, the "workaround" is
-     * possible in both, Maven and Maven Daemon: force single threaded 
execution model while "recording" (in mvn:
-     * do not pass any {@code -T} CLI parameter, while for mvnd use {@code -1} 
CLI parameter.
-     * 
-     * TODO: this will need to be reworked for at least two reasons: a) avoid 
duplicates in summary file and b)
-     * support multi threaded builds (probably will need "on session close" 
hook).
-     */
     private class SummaryFileWriter implements Writer
     {
         private final Path basedir;
 
         private final boolean originAware;
 
-        private SummaryFileWriter( Path basedir, boolean originAware )
+        private final ConcurrentHashMap<Path, Set<String>> recordedLines;
+
+        private SummaryFileWriter( Path basedir,
+                                   boolean originAware,
+                                   ConcurrentHashMap<Path, Set<String>> 
recordedLines )
         {
             this.basedir = basedir;
             this.originAware = originAware;
+            this.recordedLines = recordedLines;
         }
 
         @Override
         public void addTrustedArtifactChecksums( Artifact artifact, 
ArtifactRepository artifactRepository,
                                                  
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
-                                                 Map<String, String> 
trustedArtifactChecksums ) throws IOException
+                                                 Map<String, String> 
trustedArtifactChecksums )
         {
             for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : 
checksumAlgorithmFactories )
             {
                 String checksum = requireNonNull(
                         trustedArtifactChecksums.get( 
checksumAlgorithmFactory.getName() ) );
-                String summaryLine = ArtifactIdUtils.toId( artifact ) + " " + 
checksum + "\n";
+                String summaryLine = ArtifactIdUtils.toId( artifact ) + " " + 
checksum;
                 Path summaryPath = basedir.resolve(
                         calculateSummaryPath( originAware, artifactRepository, 
checksumAlgorithmFactory ) );
-                Files.createDirectories( summaryPath.getParent() );
-                Files.write( summaryPath, summaryLine.getBytes( 
StandardCharsets.UTF_8 ),
-                        StandardOpenOption.CREATE, StandardOpenOption.WRITE, 
StandardOpenOption.APPEND );
+
+                recordedLines.computeIfAbsent( summaryPath, p -> 
Collections.synchronizedSet( new TreeSet<>() ) )
+                        .add( summaryLine );
             }
         }
 
@@ -245,4 +255,35 @@ public final class SummaryFileTrustedChecksumsSource
             // nop
         }
     }
+
+    @SuppressWarnings( "unchecked" )
+    private void saveSessionRecordedLines( RepositorySystemSession session )
+    {
+        Map<Path, Set<String>> recordedLines = (Map<Path, Set<String>>) 
session.getData().get( RECORDING_KEY );
+        if ( recordedLines == null || recordedLines.isEmpty() )
+        {
+            return;
+        }
+
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for ( Map.Entry<Path, Set<String>> entry : recordedLines.entrySet() )
+        {
+            Path file = entry.getKey();
+            Set<String> lines = entry.getValue();
+            if ( !lines.isEmpty() )
+            {
+                LOGGER.info( "Saving {} checksums to '{}'", lines.size(), file 
);
+                try
+                {
+                    Files.createDirectories( file.getParent() );
+                    Files.write( file, lines );
+                }
+                catch ( IOException e )
+                {
+                    exceptions.add( e );
+                }
+            }
+        }
+        MultiRuntimeException.mayThrow( "session save checksums failure", 
exceptions );
+    }
 }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
index 65ba3d73..d7f6bd6c 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/synccontext/DefaultSyncContextFactory.java
@@ -19,14 +19,12 @@ package org.eclipse.aether.internal.impl.synccontext;
  * under the License.
  */
 
-import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.SyncContext;
@@ -46,8 +44,6 @@ import org.eclipse.aether.spi.locator.Service;
 import org.eclipse.aether.spi.locator.ServiceLocator;
 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
 import org.eclipse.aether.util.ConfigUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import static java.util.Objects.requireNonNull;
 
@@ -59,8 +55,6 @@ import static java.util.Objects.requireNonNull;
 public final class DefaultSyncContextFactory
         implements SyncContextFactory, Service
 {
-    private static final Logger LOGGER = LoggerFactory.getLogger( 
DefaultSyncContextFactory.class );
-
     private static final String ADAPTER_KEY = 
DefaultSyncContextFactory.class.getName() + ".adapter";
 
     private static final String NAME_MAPPER_KEY = 
"aether.syncContext.named.nameMapper";
@@ -75,8 +69,6 @@ public final class DefaultSyncContextFactory
 
     private Map<String, NamedLockFactory> namedLockFactories;
 
-    private final CopyOnWriteArrayList<NamedLockFactoryAdapter> 
createdAdapters = new CopyOnWriteArrayList<>();
-
     /**
      * Constructor used with DI, where factories are injected and selected 
based on key.
      */
@@ -122,50 +114,39 @@ public final class DefaultSyncContextFactory
     public SyncContext newInstance( final RepositorySystemSession session, 
final boolean shared )
     {
         requireNonNull( session, "session cannot be null" );
-        NamedLockFactoryAdapter adapter =
-                (NamedLockFactoryAdapter) session.getData().computeIfAbsent(
-                        ADAPTER_KEY,
-                        () -> createAdapter( session )
-                );
+        NamedLockFactoryAdapter adapter = getOrCreateSessionAdapter( session );
         return adapter.newInstance( session, shared );
     }
 
-    private NamedLockFactoryAdapter createAdapter( final 
RepositorySystemSession session )
+    private NamedLockFactoryAdapter getOrCreateSessionAdapter( final 
RepositorySystemSession session )
     {
-        String nameMapperName = ConfigUtils.getString( session, 
DEFAULT_NAME_MAPPER_NAME, NAME_MAPPER_KEY );
-        String namedLockFactoryName = ConfigUtils.getString( session, 
DEFAULT_FACTORY_NAME, FACTORY_KEY );
-        NameMapper nameMapper = nameMappers.get( nameMapperName );
-        if ( nameMapper == null )
-        {
-            throw new IllegalArgumentException( "Unknown NameMapper name: " + 
nameMapperName
-                    + ", known ones: " + nameMappers.keySet() );
-        }
-        NamedLockFactory namedLockFactory = namedLockFactories.get( 
namedLockFactoryName );
-        if ( namedLockFactory == null )
+        return (NamedLockFactoryAdapter) session.getData().computeIfAbsent( 
ADAPTER_KEY, () ->
         {
-            throw new IllegalArgumentException( "Unknown NamedLockFactory 
name: " + namedLockFactoryName
-                    + ", known ones: " + namedLockFactories.keySet() );
-        }
-        NamedLockFactoryAdapter adapter = new NamedLockFactoryAdapter( 
nameMapper, namedLockFactory );
-        createdAdapters.add( adapter );
-        return adapter;
+            String nameMapperName = ConfigUtils.getString( session, 
DEFAULT_NAME_MAPPER_NAME, NAME_MAPPER_KEY );
+            String namedLockFactoryName = ConfigUtils.getString( session, 
DEFAULT_FACTORY_NAME, FACTORY_KEY );
+            NameMapper nameMapper = nameMappers.get( nameMapperName );
+            if ( nameMapper == null )
+            {
+                throw new IllegalArgumentException( "Unknown NameMapper name: 
" + nameMapperName
+                        + ", known ones: " + nameMappers.keySet() );
+            }
+            NamedLockFactory namedLockFactory = namedLockFactories.get( 
namedLockFactoryName );
+            if ( namedLockFactory == null )
+            {
+                throw new IllegalArgumentException( "Unknown NamedLockFactory 
name: " + namedLockFactoryName
+                        + ", known ones: " + namedLockFactories.keySet() );
+            }
+            session.addOnCloseHandler( this::shutDownSessionAdapter );
+            return new NamedLockFactoryAdapter( nameMapper, namedLockFactory );
+        } );
     }
 
-    @PreDestroy
-    public void shutdown()
+    private void shutDownSessionAdapter( RepositorySystemSession session )
     {
-        LOGGER.debug( "Shutting down created adapters..." );
-        createdAdapters.forEach( adapter ->
-                {
-                    try
-                    {
-                        adapter.shutdown();
-                    }
-                    catch ( Exception e )
-                    {
-                        LOGGER.warn( "Could not shutdown: {}", adapter, e );
-                    }
-                }
-        );
+        NamedLockFactoryAdapter adapter = (NamedLockFactoryAdapter) 
session.getData().get( ADAPTER_KEY );
+        if ( adapter != null )
+        {
+            adapter.shutdown();
+        }
     }
 }


Reply via email to