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 6e74e9ee [MRESOLVER-276][MRESOLVER-268] Post artifact resolve hook and 
resolver checksum verification (#200)
6e74e9ee is described below

commit 6e74e9ee279f827140553846b6421fe9b75d3ad9
Author: Tamas Cservenak <[email protected]>
AuthorDate: Tue Oct 11 08:52:05 2022 +0200

    [MRESOLVER-276][MRESOLVER-268] Post artifact resolve hook and resolver 
checksum verification (#200)
    
    Introduce "post artifact resolve hook" feature: is able to post process
    resolved artifacts just before they are returned to caller and affect
    resolution outcome by signaling failure (by augmenting results).
    Adds one implementation as well.
    
    High level changes:
    * introduced `trusted-checksum` post processor: uses 
`TrustedChecksumsSource`
      to validate ALL resolved artifacts against available "trusted"
      checksums source. Added configuration options like "checksumAlgorithm"
      (def: sha1), "failIfMissing" (def: false) and "record" (def: false).
    * extended `TrustedChecksumsSource` to be able to write checksums as well
      (when post-processor "records")
    
    When the `trusted-checksum` post processor is enabled, it will use any
    enabled `TrustedChecksumSource` to get checksums, and match the calculated
    checksum with provided ones. If no enabled source, nothing happens (as no
    source to compare checksums with). By default missing checksum does not
    fail, only mismatches. One can turn on `failIfMissing` to achieve this
    stricter behaviour and enforce checksums on all post-processed artifacts.
    
    Finally, as possibility (for pre-populating checksusm or just testing),
    the `record` option is added, that post-processes all resolved artifacts
    by calculating their checksums, and stores them in any enabled trusted
    checksum source (both file backed implementations are extended to
    support this). Again, if no trusted checksum source enabled (or none of
    enabled ones are writable), record operation will result in "no-op".
    In current PR the summary-file trusted checksum source will produce
    overlapping (many many double checksums) files, as there is no logic
    added to enforce "uniqueness" of summary file.
    
    ---
    
    https://issues.apache.org/jira/browse/MRESOLVER-276
    https://issues.apache.org/jira/browse/MRESOLVER-268
---
 .../eclipse/aether/impl/guice/AetherModule.java    |  17 ++
 .../internal/impl/DefaultArtifactResolver.java     |  28 ++-
 .../impl/Maven2RepositoryLayoutFactory.java        |  16 +-
 .../DefaultChecksumAlgorithmFactorySelector.java   |   6 +-
 .../FileTrustedChecksumsSourceSupport.java         |  49 ++++-
 .../SparseDirectoryTrustedChecksumsSource.java     |  79 +++++--
 .../SummaryFileTrustedChecksumsSource.java         | 121 +++++++++--
 .../ArtifactResolverPostProcessorSupport.java      |  64 ++++++
 ...stedChecksumsArtifactResolverPostProcessor.java | 231 +++++++++++++++++++++
 .../internal/impl/DefaultArtifactResolverTest.java |   2 +
 .../FileTrustedChecksumsSourceTestSupport.java     |  20 +-
 .../SparseDirectoryTrustedChecksumsSourceTest.java |   8 +-
 .../SummaryFileTrustedChecksumsSourceTest.java     |   8 +-
 ...ChecksumsArtifactResolverPostProcessorTest.java | 226 ++++++++++++++++++++
 .../spi/checksums/TrustedChecksumsSource.java      |  32 ++-
 .../checksum/ChecksumAlgorithmFactorySelector.java |  16 ++
 .../checksum/ProvidedChecksumsSource.java          |  12 +-
 .../resolution/ArtifactResolverPostProcessor.java  |  47 +++++
 .../java/org/eclipse/aether/util/ConfigUtils.java  |  30 +++
 19 files changed, 940 insertions(+), 72 deletions(-)

diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
index 2e927dea..e35c2e30 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java
@@ -57,6 +57,7 @@ import 
org.eclipse.aether.internal.impl.checksum.TrustedToProvidedChecksumsSourc
 import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate;
 import org.eclipse.aether.internal.impl.collect.bf.BfDependencyCollector;
 import org.eclipse.aether.internal.impl.collect.df.DfDependencyCollector;
+import 
org.eclipse.aether.internal.impl.resolution.TrustedChecksumsArtifactResolverPostProcessor;
 import org.eclipse.aether.internal.impl.synccontext.DefaultSyncContextFactory;
 import org.eclipse.aether.internal.impl.synccontext.named.NameMapper;
 import 
org.eclipse.aether.internal.impl.synccontext.named.providers.DiscriminatingNameMapperProvider;
@@ -103,6 +104,7 @@ import 
org.eclipse.aether.spi.connector.transport.TransporterProvider;
 import org.eclipse.aether.spi.io.FileProcessor;
 import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
 import org.eclipse.aether.spi.log.LoggerFactory;
+import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
 import org.slf4j.ILoggerFactory;
 
@@ -200,6 +202,10 @@ public class AetherModule
         bind( TrustedChecksumsSource.class ).annotatedWith( Names.named( 
SummaryFileTrustedChecksumsSource.NAME ) )
                 .to( SummaryFileTrustedChecksumsSource.class ).in( 
Singleton.class );
 
+        bind( ArtifactResolverPostProcessor.class )
+                .annotatedWith( Names.named( 
TrustedChecksumsArtifactResolverPostProcessor.NAME ) )
+                .to( TrustedChecksumsArtifactResolverPostProcessor.class ).in( 
Singleton.class );
+
         bind( ChecksumAlgorithmFactory.class ).annotatedWith( Names.named( 
Md5ChecksumAlgorithmFactory.NAME ) )
                 .to( Md5ChecksumAlgorithmFactory.class );
         bind( ChecksumAlgorithmFactory.class ).annotatedWith( Names.named( 
Sha1ChecksumAlgorithmFactory.NAME ) )
@@ -240,6 +246,17 @@ public class AetherModule
 
     }
 
+    @Provides
+    @Singleton
+    Map<String, ArtifactResolverPostProcessor> artifactResolverProcessors(
+            @Named( TrustedChecksumsArtifactResolverPostProcessor.NAME ) 
ArtifactResolverPostProcessor trustedChecksums
+    )
+    {
+        Map<String, ArtifactResolverPostProcessor> result = new HashMap<>();
+        result.put( TrustedChecksumsArtifactResolverPostProcessor.NAME, 
trustedChecksums );
+        return Collections.unmodifiableMap( result );
+    }
+
     @Provides
     @Singleton
     Map<String, DependencyCollectorDelegate> dependencyCollectorDelegates(
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java
index 73400033..ac45cfee 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java
@@ -30,6 +30,9 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.eclipse.aether.RepositoryEvent;
@@ -44,6 +47,8 @@ import org.eclipse.aether.impl.OfflineController;
 import org.eclipse.aether.impl.RemoteRepositoryManager;
 import org.eclipse.aether.impl.RepositoryConnectorProvider;
 import org.eclipse.aether.impl.RepositoryEventDispatcher;
+import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
+import org.eclipse.aether.spi.synccontext.SyncContextFactory;
 import org.eclipse.aether.impl.UpdateCheck;
 import org.eclipse.aether.impl.UpdateCheckManager;
 import org.eclipse.aether.impl.VersionResolver;
@@ -68,7 +73,6 @@ import org.eclipse.aether.spi.connector.RepositoryConnector;
 import org.eclipse.aether.spi.io.FileProcessor;
 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.transfer.ArtifactNotFoundException;
 import org.eclipse.aether.transfer.ArtifactTransferException;
 import org.eclipse.aether.transfer.NoRepositoryConnectorException;
@@ -77,8 +81,6 @@ import org.eclipse.aether.util.ConfigUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static java.util.Objects.requireNonNull;
-
 /**
  *
  */
@@ -108,6 +110,8 @@ public class DefaultArtifactResolver
 
     private OfflineController offlineController;
 
+    private Map<String, ArtifactResolverPostProcessor> 
artifactResolverPostProcessors;
+
     public DefaultArtifactResolver()
     {
         // enables default constructor
@@ -119,7 +123,8 @@ public class DefaultArtifactResolver
                              VersionResolver versionResolver, 
UpdateCheckManager updateCheckManager,
                              RepositoryConnectorProvider 
repositoryConnectorProvider,
                              RemoteRepositoryManager remoteRepositoryManager, 
SyncContextFactory syncContextFactory,
-                             OfflineController offlineController )
+                             OfflineController offlineController,
+                             Map<String, ArtifactResolverPostProcessor> 
artifactResolverPostProcessors )
     {
         setFileProcessor( fileProcessor );
         setRepositoryEventDispatcher( repositoryEventDispatcher );
@@ -129,6 +134,7 @@ public class DefaultArtifactResolver
         setRemoteRepositoryManager( remoteRepositoryManager );
         setSyncContextFactory( syncContextFactory );
         setOfflineController( offlineController );
+        setArtifactResolverPostProcessors( artifactResolverPostProcessors );
     }
 
     public void initService( ServiceLocator locator )
@@ -141,6 +147,7 @@ public class DefaultArtifactResolver
         setRemoteRepositoryManager( locator.getService( 
RemoteRepositoryManager.class ) );
         setSyncContextFactory( locator.getService( SyncContextFactory.class ) 
);
         setOfflineController( locator.getService( OfflineController.class ) );
+        setArtifactResolverPostProcessors( Collections.emptyMap() );
     }
 
     /**
@@ -205,6 +212,14 @@ public class DefaultArtifactResolver
         return this;
     }
 
+    public DefaultArtifactResolver setArtifactResolverPostProcessors(
+            Map<String, ArtifactResolverPostProcessor> 
artifactResolverPostProcessors )
+    {
+        this.artifactResolverPostProcessors = requireNonNull( 
artifactResolverPostProcessors,
+                "artifact resolver post-processors cannot be null" );
+        return this;
+    }
+
     public ArtifactResult resolveArtifact( RepositorySystemSession session, 
ArtifactRequest request )
             throws ArtifactResolutionException
     {
@@ -410,6 +425,11 @@ public class DefaultArtifactResolver
             performDownloads( session, group );
         }
 
+        for ( ArtifactResolverPostProcessor artifactResolverPostProcessor : 
artifactResolverPostProcessors.values() )
+        {
+            artifactResolverPostProcessor.postProcess( session, results );
+        }
+
         for ( ArtifactResult result : results )
         {
             ArtifactRequest request = result.getRequest();
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java
index 1b76940e..6ef37964 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/Maven2RepositoryLayoutFactory.java
@@ -24,7 +24,6 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -110,18 +109,11 @@ public final class Maven2RepositoryLayoutFactory
         {
             throw new NoRepositoryLayoutException( repository );
         }
-        // ensure order and uniqueness of (potentially user set) algorithm list
-        LinkedHashSet<String> checksumsAlgorithmNames = Arrays.stream( 
ConfigUtils.getString(
-                        session, DEFAULT_CHECKSUMS_ALGORITHMS, 
CONFIG_PROP_CHECKSUMS_ALGORITHMS )
-                .split( "," )
-        ).filter( s -> s != null && !s.trim().isEmpty() ).collect( 
Collectors.toCollection( LinkedHashSet::new ) );
 
-        // validation: this loop implicitly validates the list above: selector 
will throw on unknown algorithm
-        List<ChecksumAlgorithmFactory> checksumsAlgorithms = new ArrayList<>( 
checksumsAlgorithmNames.size() );
-        for ( String checksumsAlgorithmName : checksumsAlgorithmNames )
-        {
-            checksumsAlgorithms.add( checksumAlgorithmFactorySelector.select( 
checksumsAlgorithmName ) );
-        }
+        List<ChecksumAlgorithmFactory> checksumsAlgorithms = 
checksumAlgorithmFactorySelector.select(
+                ConfigUtils.parseCommaSeparatedUniqueNames( 
ConfigUtils.getString(
+                        session, DEFAULT_CHECKSUMS_ALGORITHMS, 
CONFIG_PROP_CHECKSUMS_ALGORITHMS ) )
+        );
 
         // ensure uniqueness of (potentially user set) extension list
         Set<String> omitChecksumsForExtensions = Arrays.stream( 
ConfigUtils.getString(
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java
index d463100b..d1b2fd7c 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/DefaultChecksumAlgorithmFactorySelector.java
@@ -69,15 +69,15 @@ public class DefaultChecksumAlgorithmFactorySelector
     public ChecksumAlgorithmFactory select( String algorithmName )
     {
         requireNonNull( algorithmName, "algorithmMame must not be null" );
-        ChecksumAlgorithmFactory factory =  factories.get( algorithmName );
+        ChecksumAlgorithmFactory factory = factories.get( algorithmName );
         if ( factory == null )
         {
             throw new IllegalArgumentException(
                     String.format( "Unsupported checksum algorithm %s, 
supported ones are %s",
                             algorithmName,
                             getChecksumAlgorithmFactories().stream()
-                                                           .map( 
ChecksumAlgorithmFactory::getName )
-                                                           .collect( toList() )
+                                    .map( ChecksumAlgorithmFactory::getName )
+                                    .collect( toList() )
                     )
             );
         }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java
index 1f023064..1ea77638 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceSupport.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -93,24 +94,53 @@ abstract class FileTrustedChecksumsSourceSupport
         boolean enabled = ConfigUtils.getBoolean( session, false, 
CONFIG_PROP_PREFIX + this.name );
         if ( enabled )
         {
-            Path basedir = getBasedir( session );
+            Path basedir = getBasedir( session, false );
             if ( basedir != null && !checksumAlgorithmFactories.isEmpty() )
             {
-                Map<String, String> result = performLookup(
-                        session, basedir, artifact, artifactRepository, 
checksumAlgorithmFactories );
-
-                return result == null || result.isEmpty() ? null : result;
+                return requireNonNull(
+                        performLookup( session, basedir, artifact, 
artifactRepository, checksumAlgorithmFactories )
+                );
+            }
+            else
+            {
+                return Collections.emptyMap();
             }
         }
         return null;
     }
 
+    @Override
+    public Writer getTrustedArtifactChecksumsWriter( RepositorySystemSession 
session )
+    {
+        requireNonNull( session, "session is null" );
+        boolean enabled = ConfigUtils.getBoolean( session, false, 
CONFIG_PROP_PREFIX + this.name );
+        if ( enabled )
+        {
+            return getWriter( session, getBasedir( session, true ) );
+        }
+        return null;
+    }
+
+    /**
+     * Implementors MUST NOT return {@code null} at this point, as the "source 
is enabled" check was already performed
+     * and IS enabled, worst can happen is checksums for asked artifact are 
not available.
+     */
     protected abstract Map<String, String> performLookup( 
RepositorySystemSession session,
                                                           Path basedir,
                                                           Artifact artifact,
                                                           ArtifactRepository 
artifactRepository,
                                                           
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories );
 
+    /**
+     * If a subclass of this support class support
+     * {@link org.eclipse.aether.spi.checksums.TrustedChecksumsSource.Writer}, 
or in other words "is writable", it
+     * should override this method and return proper instance.
+     */
+    protected Writer getWriter( RepositorySystemSession session, Path basedir )
+    {
+        return null;
+    }
+
     /**
      * To be used by underlying implementations to form configuration property 
keys properly scoped.
      */
@@ -129,15 +159,16 @@ abstract class FileTrustedChecksumsSourceSupport
     }
 
     /**
-     * Uses common {@link DirectoryUtils} to calculate (but not) create 
basedir for this implementation. Returns
-     * {@code null} if the calculated basedir does not exist.
+     * Uses common {@link DirectoryUtils} to calculate (and maybe create) 
basedir for this implementation. Returns
+     * {@code null} if the calculated basedir does not exist and {@code 
mayCreate} was {@code false}. If
+     * {@code mayCreate} parameter was {@code true}, this method always 
returns non-null {@link Path} or throws.
      */
-    private Path getBasedir( RepositorySystemSession session )
+    private Path getBasedir( RepositorySystemSession session, boolean 
mayCreate )
     {
         try
         {
             Path basedir = DirectoryUtils.resolveDirectory(
-                    session, LOCAL_REPO_PREFIX_DIR, configPropKey( 
CONF_NAME_BASEDIR ), false );
+                    session, LOCAL_REPO_PREFIX_DIR, configPropKey( 
CONF_NAME_BASEDIR ), mayCreate );
             if ( !Files.isDirectory( basedir ) )
             {
                 return null;
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java
index e8f88d29..1c28de08 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java
@@ -24,6 +24,7 @@ import javax.inject.Named;
 import javax.inject.Singleton;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
@@ -82,26 +83,16 @@ public final class SparseDirectoryTrustedChecksumsSource
                                                  ArtifactRepository 
artifactRepository,
                                                  
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories )
     {
-        final String prefix;
-        if ( isOriginAware( session ) )
-        {
-            prefix = artifactRepository.getId() + "/";
-        }
-        else
-        {
-            prefix = "";
-        }
-
+        final boolean originAware = isOriginAware( session );
         final HashMap<String, String> checksums = new HashMap<>();
-        final String artifactPath = localPathComposer.getPathForArtifact( 
artifact, false );
         for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : 
checksumAlgorithmFactories )
         {
             Path checksumPath = basedir.resolve(
-                    prefix + artifactPath + "." + 
checksumAlgorithmFactory.getFileExtension() );
+                    calculateArtifactPath( originAware, artifact, 
artifactRepository, checksumAlgorithmFactory ) );
 
             if ( !Files.isRegularFile( checksumPath ) )
             {
-                LOGGER.debug( "Artifact '{}' checksum '{}' not found in path 
'{}'",
+                LOGGER.debug( "Artifact '{}' trusted checksum '{}' not found 
on path '{}'",
                         artifact, checksumAlgorithmFactory.getName(), 
checksumPath );
                 continue;
             }
@@ -117,9 +108,69 @@ public final class SparseDirectoryTrustedChecksumsSource
             catch ( IOException e )
             {
                 // unexpected, log, skip
-                LOGGER.warn( "Could not read provided checksum for '{}' at 
path '{}'", artifact, checksumPath, e );
+                LOGGER.warn( "Could not read artifact '{}' trusted checksum on 
path '{}'", artifact, checksumPath, e );
             }
         }
         return checksums;
     }
+
+    @Override
+    protected SparseDirectoryWriter getWriter( RepositorySystemSession 
session, Path basedir )
+    {
+        return new SparseDirectoryWriter( basedir, isOriginAware( session ) );
+    }
+
+    private String calculateArtifactPath( boolean originAware,
+                                          Artifact artifact,
+                                          ArtifactRepository 
artifactRepository,
+                                          ChecksumAlgorithmFactory 
checksumAlgorithmFactory )
+    {
+        final String prefix;
+        if ( originAware )
+        {
+            prefix = artifactRepository.getId() + "/";
+        }
+        else
+        {
+            prefix = "";
+        }
+
+        return prefix + localPathComposer.getPathForArtifact( artifact, false )
+                + "." + checksumAlgorithmFactory.getFileExtension();
+    }
+
+    private class SparseDirectoryWriter implements Writer
+    {
+        private final Path basedir;
+
+        private final boolean originAware;
+
+        private SparseDirectoryWriter( Path basedir, boolean originAware )
+        {
+            this.basedir = basedir;
+            this.originAware = originAware;
+        }
+
+        @Override
+        public void addTrustedArtifactChecksums( Artifact artifact, 
ArtifactRepository artifactRepository,
+                                                 
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
+                                                 Map<String, String> 
trustedArtifactChecksums ) throws IOException
+        {
+            for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : 
checksumAlgorithmFactories )
+            {
+                String checksum = requireNonNull(
+                        trustedArtifactChecksums.get( 
checksumAlgorithmFactory.getName() ) );
+                Path checksumPath = basedir.resolve( calculateArtifactPath(
+                        originAware, artifact, artifactRepository, 
checksumAlgorithmFactory ) );
+                Files.createDirectories( checksumPath.getParent() );
+                Files.write( checksumPath, checksum.getBytes( 
StandardCharsets.UTF_8 ) );
+            }
+        }
+
+        @Override
+        public void close()
+        {
+            // nop
+        }
+    }
 }
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 9379165d..d6f7e21c 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,9 +29,11 @@ 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.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.eclipse.aether.RepositorySystemSession;
@@ -42,6 +44,8 @@ import org.eclipse.aether.util.artifact.ArtifactIdUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * Compact file {@link FileTrustedChecksumsSourceSupport} implementation that 
use specified directory as base
  * directory, where it expects a "summary" file named as 
"checksums.${checksumExt}" for each checksum algorithm, and
@@ -85,16 +89,7 @@ public final class SummaryFileTrustedChecksumsSource
                                                  ArtifactRepository 
artifactRepository,
                                                  
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories )
     {
-        final String fileName;
-        if ( isOriginAware( session ) )
-        {
-            fileName = CHECKSUMS_FILE_PREFIX + "-" + 
artifactRepository.getId();
-        }
-        else
-        {
-            fileName = CHECKSUMS_FILE_PREFIX;
-        }
-
+        final boolean originAware = isOriginAware( session );
         final ConcurrentHashMap<String, ConcurrentHashMap<String, String>> 
basedirProvidedChecksums =
                 (ConcurrentHashMap<String, ConcurrentHashMap<String, String>>) 
session.getData()
                         .computeIfAbsent( CHECKSUMS_CACHE_KEY, 
ConcurrentHashMap::new );
@@ -105,8 +100,7 @@ public final class SummaryFileTrustedChecksumsSource
             ConcurrentHashMap<String, String> algorithmChecksums = 
basedirProvidedChecksums.computeIfAbsent(
                     checksumAlgorithmFactory.getName(),
                     algName -> loadProvidedChecksums(
-                            basedir.resolve( fileName + "." + 
checksumAlgorithmFactory.getFileExtension() )
-                    )
+                            basedir, originAware, artifactRepository, 
checksumAlgorithmFactory )
             );
             String checksum = algorithmChecksums.get( ArtifactIdUtils.toId( 
artifact ) );
             if ( checksum != null )
@@ -117,14 +111,26 @@ public final class SummaryFileTrustedChecksumsSource
         return checksums;
     }
 
-    private ConcurrentHashMap<String, String> loadProvidedChecksums( Path 
checksumsFile )
+    @Override
+    protected SummaryFileWriter getWriter( RepositorySystemSession session, 
Path basedir )
     {
+        return new SummaryFileWriter( basedir, isOriginAware( session ) );
+    }
+
+    private ConcurrentHashMap<String, String> loadProvidedChecksums( Path 
basedir,
+                                                                     boolean 
originAware,
+                                                                     
ArtifactRepository artifactRepository,
+                                                                     
ChecksumAlgorithmFactory checksumAlgorithmFactory )
+    {
+        Path checksumsFile = basedir.resolve(
+                calculateSummaryPath( originAware, artifactRepository, 
checksumAlgorithmFactory ) );
         ConcurrentHashMap<String, String> result = new ConcurrentHashMap<>();
         if ( Files.isReadable( checksumsFile ) )
         {
             try ( BufferedReader reader = Files.newBufferedReader( 
checksumsFile, StandardCharsets.UTF_8 ) )
             {
-                LOGGER.debug( "Loading provided checksums file '{}'", 
checksumsFile );
+                LOGGER.debug( "Loading {} trusted checksums for remote 
repository {} from '{}'",
+                        checksumAlgorithmFactory.getName(), 
artifactRepository.getId(), checksumsFile );
                 String line;
                 while ( ( line = reader.readLine() ) != null )
                 {
@@ -133,11 +139,21 @@ public final class SummaryFileTrustedChecksumsSource
                         String[] parts = line.split( " ", 2 );
                         if ( parts.length == 2 )
                         {
-                            String old = result.put( parts[0], parts[1] );
-                            if ( old != null )
+                            String newChecksum = parts[1];
+                            String oldChecksum = result.put( parts[0], 
newChecksum );
+                            if ( oldChecksum != null )
                             {
-                                LOGGER.warn( "Checksums file '{}' contains 
duplicate checksums for artifact {}: "
-                                        + "old '{}' replaced by new '{}'", 
checksumsFile, parts[0], old, parts[1] );
+                                if ( Objects.equals( oldChecksum, newChecksum 
) )
+                                {
+                                    LOGGER.warn( "Checksums file '{}' contains 
duplicate checksums for artifact {}: {}",
+                                            checksumsFile, parts[0], 
oldChecksum );
+                                }
+                                else
+                                {
+                                    LOGGER.warn( "Checksums file '{}' contains 
different checksums for artifact {}: "
+                                                    + "old '{}' replaced by 
new '{}'", checksumsFile, parts[0],
+                                            oldChecksum, newChecksum );
+                                }
                             }
                         }
                         else
@@ -146,11 +162,14 @@ public final class SummaryFileTrustedChecksumsSource
                         }
                     }
                 }
+                LOGGER.info( "Loaded {} {} trusted checksums for remote 
repository {}",
+                        result.size(), checksumAlgorithmFactory.getName(), 
artifactRepository.getId() );
             }
             catch ( NoSuchFileException e )
             {
                 // strange: we tested for it above, still, we should not fail
-                LOGGER.debug( "Checksums file '{}' not found", checksumsFile );
+                LOGGER.debug( "The {} trusted checksums for remote repository 
{} not exist at '{}'",
+                        checksumAlgorithmFactory.getName(), 
artifactRepository.getId(), checksumsFile );
             }
             catch ( IOException e )
             {
@@ -159,9 +178,71 @@ public final class SummaryFileTrustedChecksumsSource
         }
         else
         {
-            LOGGER.debug( "Checksums file '{}' not found", checksumsFile );
+            LOGGER.debug( "The {} trusted checksums for remote repository {} 
not exist at '{}'",
+                    checksumAlgorithmFactory.getName(), 
artifactRepository.getId(), checksumsFile );
         }
 
         return result;
     }
+
+    private String calculateSummaryPath( boolean originAware,
+                                         ArtifactRepository artifactRepository,
+                                         ChecksumAlgorithmFactory 
checksumAlgorithmFactory )
+    {
+        final String fileName;
+        if ( originAware )
+        {
+            fileName = CHECKSUMS_FILE_PREFIX + "-" + 
artifactRepository.getId();
+        }
+        else
+        {
+            fileName = CHECKSUMS_FILE_PREFIX;
+        }
+        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 )
+        {
+            this.basedir = basedir;
+            this.originAware = originAware;
+        }
+
+        @Override
+        public void addTrustedArtifactChecksums( Artifact artifact, 
ArtifactRepository artifactRepository,
+                                                 
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
+                                                 Map<String, String> 
trustedArtifactChecksums ) throws IOException
+        {
+            for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : 
checksumAlgorithmFactories )
+            {
+                String checksum = requireNonNull(
+                        trustedArtifactChecksums.get( 
checksumAlgorithmFactory.getName() ) );
+                String summaryLine = ArtifactIdUtils.toId( artifact ) + " " + 
checksum + "\n";
+                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 );
+            }
+        }
+
+        @Override
+        public void close()
+        {
+            // nop
+        }
+    }
 }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/resolution/ArtifactResolverPostProcessorSupport.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/resolution/ArtifactResolverPostProcessorSupport.java
new file mode 100644
index 00000000..4b09f27b
--- /dev/null
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/resolution/ArtifactResolverPostProcessorSupport.java
@@ -0,0 +1,64 @@
+package org.eclipse.aether.internal.impl.resolution;
+
+/*
+ * 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 org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
+import org.eclipse.aether.util.ConfigUtils;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Support class to implement {@link ArtifactResolverPostProcessor}.
+ *
+ * @since TBD
+ */
+public abstract class ArtifactResolverPostProcessorSupport
+        implements ArtifactResolverPostProcessor
+{
+    private static final String CONFIG_PROP_PREFIX = 
"aether.artifactResolver.postProcessor.";
+
+    private final String name;
+
+    protected ArtifactResolverPostProcessorSupport( String name )
+    {
+        this.name = requireNonNull( name );
+    }
+
+    protected String configPropKey( String name )
+    {
+        return CONFIG_PROP_PREFIX + this.name + "." + name;
+    }
+
+    @Override
+    public void postProcess( RepositorySystemSession session, 
List<ArtifactResult> artifactResults )
+    {
+        boolean enabled = ConfigUtils.getBoolean( session, false, 
CONFIG_PROP_PREFIX + this.name );
+        if ( enabled )
+        {
+            doProcess( session, artifactResults );
+        }
+    }
+
+    protected abstract void doProcess( RepositorySystemSession session, 
List<ArtifactResult> artifactResults );
+}
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/resolution/TrustedChecksumsArtifactResolverPostProcessor.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/resolution/TrustedChecksumsArtifactResolverPostProcessor.java
new file mode 100644
index 00000000..ab2b87d2
--- /dev/null
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/resolution/TrustedChecksumsArtifactResolverPostProcessor.java
@@ -0,0 +1,231 @@
+package org.eclipse.aether.internal.impl.resolution;
+
+/*
+ * 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 javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.repository.ArtifactRepository;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.spi.checksums.TrustedChecksumsSource;
+import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
+import 
org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
+import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper;
+import org.eclipse.aether.transfer.ChecksumFailureException;
+import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.artifact.ArtifactIdUtils;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Artifact resolver processor that verifies the checksums of all resolved 
artifacts against trusted checksums. Is also
+ * able to "record" (calculate and write them) to trusted checksum sources, 
that do support this operation.
+ *
+ * @since TBD
+ */
+@Singleton
+@Named( TrustedChecksumsArtifactResolverPostProcessor.NAME )
+public final class TrustedChecksumsArtifactResolverPostProcessor
+        extends ArtifactResolverPostProcessorSupport
+{
+    public static final String NAME = "trusted-checksums";
+
+    private static final String CONF_CHECKSUM_ALGORITHMS = 
"checksumAlgorithms";
+
+    private static final String DEFAULT_CHECKSUM_ALGORITHMS = "SHA-1";
+
+    private static final String CONF_FAIL_IF_MISSING = "failIfMissing";
+
+    private static final String CONF_RECORD = "record";
+
+    private static final String CHECKSUM_ALGORITHMS_CACHE_KEY =
+            TrustedChecksumsArtifactResolverPostProcessor.class.getName() + 
".checksumAlgorithms";
+
+    private final ChecksumAlgorithmFactorySelector 
checksumAlgorithmFactorySelector;
+
+    private final Map<String, TrustedChecksumsSource> trustedChecksumsSources;
+
+    @Inject
+    public TrustedChecksumsArtifactResolverPostProcessor(
+            ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector,
+            Map<String, TrustedChecksumsSource> trustedChecksumsSources )
+    {
+        super( NAME );
+        this.checksumAlgorithmFactorySelector = requireNonNull( 
checksumAlgorithmFactorySelector );
+        this.trustedChecksumsSources = requireNonNull( trustedChecksumsSources 
);
+    }
+
+    @SuppressWarnings( "unchecked" )
+    @Override
+    protected void doProcess( RepositorySystemSession session, 
List<ArtifactResult> artifactResults )
+    {
+        final List<ChecksumAlgorithmFactory> checksumAlgorithms = 
(List<ChecksumAlgorithmFactory>) session.getData()
+                .computeIfAbsent( CHECKSUM_ALGORITHMS_CACHE_KEY, () ->
+                        checksumAlgorithmFactorySelector.select(
+                                ConfigUtils.parseCommaSeparatedUniqueNames( 
ConfigUtils.getString(
+                                        session, DEFAULT_CHECKSUM_ALGORITHMS, 
CONF_CHECKSUM_ALGORITHMS ) )
+                        ) );
+
+        final boolean failIfMissing = ConfigUtils.getBoolean(
+                session, false, configPropKey( CONF_FAIL_IF_MISSING ) );
+        final boolean record = ConfigUtils.getBoolean( session, false, 
configPropKey( CONF_RECORD ) );
+
+        for ( ArtifactResult artifactResult : artifactResults )
+        {
+            if ( artifactResult.isResolved() )
+            {
+                if ( record )
+                {
+                    recordArtifactChecksums( session, artifactResult, 
checksumAlgorithms );
+                }
+                else if ( !validateArtifactChecksums( session, artifactResult, 
checksumAlgorithms, failIfMissing ) )
+                {
+                    artifactResult.setArtifact( 
artifactResult.getArtifact().setFile( null ) ); // make it unresolved
+                }
+            }
+        }
+    }
+
+    /**
+     * Calculates and records checksums into trusted sources that support 
writing.
+     */
+    private void recordArtifactChecksums( RepositorySystemSession session,
+                                          ArtifactResult artifactResult,
+                                          List<ChecksumAlgorithmFactory> 
checksumAlgorithmFactories )
+    {
+        Artifact artifact = artifactResult.getArtifact();
+        ArtifactRepository artifactRepository = artifactResult.getRepository();
+
+        try
+        {
+            final Map<String, String> calculatedChecksums = 
ChecksumAlgorithmHelper.calculate(
+                    artifact.getFile(), checksumAlgorithmFactories );
+
+            for ( TrustedChecksumsSource trustedChecksumsSource : 
trustedChecksumsSources.values() )
+            {
+                try ( TrustedChecksumsSource.Writer writer = 
trustedChecksumsSource
+                        .getTrustedArtifactChecksumsWriter( session ) )
+                {
+                    if ( writer != null )
+                    {
+                        writer.addTrustedArtifactChecksums( artifact, 
artifactRepository, checksumAlgorithmFactories,
+                                calculatedChecksums );
+                    }
+                }
+            }
+        }
+        catch ( IOException e )
+        {
+            throw new UncheckedIOException( "Could not calculate amd write 
required checksums for "
+                    + artifact.getFile(), e );
+        }
+    }
+
+    /**
+     * Validates trusted checksums against {@link ArtifactResult}, returns 
{@code true} denoting "valid" checksums or
+     * {@code false} denoting "invalid" checksums.
+     */
+    private boolean validateArtifactChecksums( RepositorySystemSession session,
+                                               ArtifactResult artifactResult,
+                                               List<ChecksumAlgorithmFactory> 
checksumAlgorithmFactories,
+                                               boolean failIfMissing )
+    {
+        Artifact artifact = artifactResult.getArtifact();
+        ArtifactRepository artifactRepository = artifactResult.getRepository();
+
+        boolean valid = true;
+        boolean validated = false;
+        try
+        {
+            // full set: calculate all algorithms we were asked for
+            final Map<String, String> calculatedChecksums = 
ChecksumAlgorithmHelper.calculate(
+                    artifact.getFile(), checksumAlgorithmFactories );
+
+            for ( Map.Entry<String, TrustedChecksumsSource> entry : 
trustedChecksumsSources.entrySet() )
+            {
+                final String trustedSourceName = entry.getKey();
+                final TrustedChecksumsSource trustedChecksumsSource = 
entry.getValue();
+
+                // upper bound set: ask source for checksums, ideally same as 
calculatedChecksums but may be less
+                Map<String, String> trustedChecksums = 
trustedChecksumsSource.getTrustedArtifactChecksums(
+                        session, artifact, artifactRepository, 
checksumAlgorithmFactories );
+
+                if ( trustedChecksums == null )
+                {
+                    continue; // not enabled
+                }
+                validated = true;
+
+                if ( !calculatedChecksums.equals( trustedChecksums ) )
+                {
+                    Set<String> missingTrustedAlg = new HashSet<>( 
calculatedChecksums.keySet() );
+                    missingTrustedAlg.removeAll( trustedChecksums.keySet() );
+
+                    if ( !missingTrustedAlg.isEmpty() && failIfMissing )
+                    {
+                        artifactResult.addException( new 
ChecksumFailureException( "Missing from " + trustedSourceName
+                                + " trusted checksum(s) " + missingTrustedAlg 
+ " for artifact "
+                                + ArtifactIdUtils.toId( artifact ) ) );
+                        valid = false;
+                    }
+
+                    // compare values but only present ones, failIfMissing 
handled above
+                    // we still want to report all: algX - missing, algY - 
mismatch, etc
+                    for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : 
checksumAlgorithmFactories )
+                    {
+                        String calculatedChecksum = calculatedChecksums.get( 
checksumAlgorithmFactory.getName() );
+                        String trustedChecksum = trustedChecksums.get( 
checksumAlgorithmFactory.getName() );
+                        if ( trustedChecksum != null && !Objects.equals( 
calculatedChecksum, trustedChecksum ) )
+                        {
+                            artifactResult.addException( new 
ChecksumFailureException( "Artifact "
+                                    + ArtifactIdUtils.toId( artifact ) + " 
trusted checksum mismatch: "
+                                    + trustedSourceName + "=" + 
trustedChecksum + "; calculated="
+                                    + calculatedChecksum ) );
+                            valid = false;
+                        }
+                    }
+                }
+            }
+
+            if ( !validated && failIfMissing )
+            {
+                artifactResult.addException( new ChecksumFailureException( 
"There are no enabled trusted checksums"
+                        + " source(s) to validate against." ) );
+                valid = false;
+            }
+        }
+        catch ( IOException e )
+        {
+            throw new UncheckedIOException( e );
+        }
+        return valid;
+    }
+}
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java
index 65ad4880..41600aaa 100644
--- 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java
@@ -25,6 +25,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -103,6 +104,7 @@ public class DefaultArtifactResolverTest
         resolver.setRemoteRepositoryManager( new StubRemoteRepositoryManager() 
);
         resolver.setSyncContextFactory( new StubSyncContextFactory() );
         resolver.setOfflineController( new DefaultOfflineController() );
+        resolver.setArtifactResolverPostProcessors( Collections.emptyMap() );
 
         artifact = new DefaultArtifact( "gid", "aid", "", "ext", "ver" );
 
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceTestSupport.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceTestSupport.java
index 6a8d4e52..12f62b55 100644
--- 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceTestSupport.java
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/FileTrustedChecksumsSourceTestSupport.java
@@ -35,6 +35,7 @@ import org.junit.Test;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 public abstract class FileTrustedChecksumsSourceTestSupport
 {
@@ -63,21 +64,38 @@ public abstract class FileTrustedChecksumsSourceTestSupport
 
     protected abstract FileTrustedChecksumsSourceSupport prepareSubject( Path 
basedir ) throws IOException;
 
+    protected abstract void enableSource();
+
+    @Test
+    public void notEnabled()
+    {
+        assertNull( subject.getTrustedArtifactChecksums(
+                        session,
+                        ARTIFACT_WITH_CHECKSUM,
+                        session.getLocalRepository(),
+                        Collections.singletonList( checksumAlgorithmFactory )
+                )
+        );
+    }
+
     @Test
     public void noProvidedArtifactChecksum()
     {
+        enableSource();
         Map<String, String> providedChecksums = 
subject.getTrustedArtifactChecksums(
                 session,
                 ARTIFACT_WITHOUT_CHECKSUM,
                 session.getLocalRepository(),
                 Collections.singletonList( checksumAlgorithmFactory )
         );
-        assertNull( providedChecksums );
+        assertNotNull( providedChecksums );
+        assertTrue( providedChecksums.isEmpty() );
     }
 
     @Test
     public void haveProvidedArtifactChecksum()
     {
+        enableSource();
         Map<String, String> providedChecksums = 
subject.getTrustedArtifactChecksums(
                 session,
                 ARTIFACT_WITH_CHECKSUM,
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java
index f1645de8..1a9e8821 100644
--- 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java
@@ -33,8 +33,6 @@ public class SparseDirectoryTrustedChecksumsSourceTest 
extends FileTrustedChecks
     @Override
     protected FileTrustedChecksumsSourceSupport prepareSubject( Path basedir ) 
throws IOException
     {
-        session.setConfigProperty( 
"aether.trustedChecksumsSource.sparse-directory",
-                Boolean.TRUE.toString() );
         LocalPathComposer localPathComposer = new DefaultLocalPathComposer();
         // artifact: test:test:2.0 => "foobar"
         {
@@ -47,4 +45,10 @@ public class SparseDirectoryTrustedChecksumsSourceTest 
extends FileTrustedChecks
 
         return new SparseDirectoryTrustedChecksumsSource( new 
DefaultFileProcessor(), localPathComposer );
     }
+
+    @Override
+    protected void enableSource()
+    {
+        session.setConfigProperty( 
"aether.trustedChecksumsSource.sparse-directory", Boolean.TRUE.toString() );
+    }
 }
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java
index a21f7ff9..9a292682 100644
--- 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java
@@ -31,8 +31,6 @@ public class SummaryFileTrustedChecksumsSourceTest extends 
FileTrustedChecksumsS
     @Override
     protected FileTrustedChecksumsSourceSupport prepareSubject( Path basedir ) 
throws IOException
     {
-        session.setConfigProperty( 
"aether.trustedChecksumsSource.summary-file",
-                Boolean.TRUE.toString() );
         // artifact: test:test:2.0 => "foobar"
         {
             Path test = basedir.resolve( "checksums." + 
checksumAlgorithmFactory.getFileExtension() );
@@ -44,4 +42,10 @@ public class SummaryFileTrustedChecksumsSourceTest extends 
FileTrustedChecksumsS
 
         return new SummaryFileTrustedChecksumsSource();
     }
+
+    @Override
+    protected void enableSource()
+    {
+        session.setConfigProperty( 
"aether.trustedChecksumsSource.summary-file", Boolean.TRUE.toString() );
+    }
 }
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/resolution/TrustedChecksumsArtifactResolverPostProcessorTest.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/resolution/TrustedChecksumsArtifactResolverPostProcessorTest.java
new file mode 100644
index 00000000..06cd721c
--- /dev/null
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/resolution/TrustedChecksumsArtifactResolverPostProcessorTest.java
@@ -0,0 +1,226 @@
+package org.eclipse.aether.internal.impl.resolution;
+
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.repository.ArtifactRepository;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.spi.checksums.TrustedChecksumsSource;
+import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
+import 
org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
+import org.eclipse.aether.util.artifact.ArtifactIdUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * UT for {@link TrustedChecksumsArtifactResolverPostProcessor}.
+ */
+public class TrustedChecksumsArtifactResolverPostProcessorTest implements 
TrustedChecksumsSource
+{
+    private static final String TRUSTED_SOURCE_NAME = "test";
+
+    private Artifact artifactWithoutTrustedChecksum;
+
+    private Artifact artifactWithTrustedChecksum;
+
+    private String artifactTrustedChecksum;
+
+    protected DefaultRepositorySystemSession session;
+
+    protected ChecksumAlgorithmFactory checksumAlgorithmFactory = new 
Sha1ChecksumAlgorithmFactory();
+
+    private TrustedChecksumsArtifactResolverPostProcessor subject;
+
+    private TrustedChecksumsSource.Writer trustedChecksumsWriter;
+
+    @Before
+    public void prepareSubject() throws IOException
+    {
+        // make the two artifacts, BOTH as resolved
+        File tmp = Files.createTempFile( "artifact", "tmp" ).toFile();
+        artifactWithoutTrustedChecksum = new DefaultArtifact( "test:test:1.0" 
).setFile( tmp );
+        artifactWithTrustedChecksum = new DefaultArtifact( "test:test:2.0" 
).setFile( tmp );
+        artifactTrustedChecksum = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; 
// empty file
+
+        session = TestUtils.newSession();
+        ChecksumAlgorithmFactorySelector selector = new 
ChecksumAlgorithmFactorySelector()
+        {
+            @Override
+            public ChecksumAlgorithmFactory select( String algorithmName )
+            {
+                if ( checksumAlgorithmFactory.getName().equals( algorithmName 
) )
+                {
+                    return checksumAlgorithmFactory;
+                }
+                throw new IllegalArgumentException("no alg factory for " + 
algorithmName);
+            }
+
+            @Override
+            public Collection<ChecksumAlgorithmFactory> 
getChecksumAlgorithmFactories()
+            {
+                return Collections.singletonList( checksumAlgorithmFactory );
+            }
+        };
+        subject = new TrustedChecksumsArtifactResolverPostProcessor( selector,
+                Collections.singletonMap( TRUSTED_SOURCE_NAME, this ) );
+        trustedChecksumsWriter = null;
+        session.setConfigProperty( 
"aether.artifactResolver.postProcessor.trusted-checksums", 
Boolean.TRUE.toString() );
+    }
+
+    // -- TrustedChecksumsSource interface BEGIN
+
+    @Override
+    public Map<String, String> getTrustedArtifactChecksums( 
RepositorySystemSession session, Artifact artifact,
+                                                            ArtifactRepository 
artifactRepository,
+                                                            
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories )
+    {
+        if ( ArtifactIdUtils.toId( artifactWithTrustedChecksum ).equals( 
ArtifactIdUtils.toId( artifact ) ) )
+        {
+            return Collections.singletonMap( 
checksumAlgorithmFactory.getName(), artifactTrustedChecksum );
+        }
+        else
+        {
+            return Collections.emptyMap();
+        }
+    }
+
+    @Override
+    public Writer getTrustedArtifactChecksumsWriter( RepositorySystemSession 
session )
+    {
+        return trustedChecksumsWriter;
+    }
+
+    // -- TrustedChecksumsSource interface END
+
+    private ArtifactResult createArtifactResult( Artifact artifact )
+    {
+        ArtifactResult artifactResult = new ArtifactResult( new 
ArtifactRequest().setArtifact( artifact ) );
+        artifactResult.setArtifact( artifact );
+        return artifactResult;
+    }
+
+    // UTs below
+
+    @Test
+    public void haveMatchingChecksumPass()
+    {
+        ArtifactResult artifactResult = createArtifactResult( 
artifactWithTrustedChecksum );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+
+        subject.postProcess( session, Collections.singletonList( 
artifactResult ) );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+    }
+
+    @Test
+    public void haveNoChecksumPass()
+    {
+        ArtifactResult artifactResult = createArtifactResult( 
artifactWithoutTrustedChecksum );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+
+        subject.postProcess( session, Collections.singletonList( 
artifactResult ) );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+    }
+
+    @Test
+    public void haveNoChecksumFailIfMissingEnabledFail()
+    {
+        session.setConfigProperty( 
"aether.artifactResolver.postProcessor.trusted-checksums.failIfMissing",
+                Boolean.TRUE.toString() );
+        ArtifactResult artifactResult = createArtifactResult( 
artifactWithoutTrustedChecksum );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+
+        subject.postProcess( session, Collections.singletonList( 
artifactResult ) );
+        assertThat( artifactResult.isResolved(), equalTo( false ) );
+        assertThat( artifactResult.getExceptions(), not( empty() ) );
+        assertThat( artifactResult.getExceptions().get( 0 ).getMessage(),
+                containsString( "Missing from " + TRUSTED_SOURCE_NAME + " 
trusted" ) );
+    }
+
+    @Test
+    public void haveMismatchingChecksumFail()
+    {
+        artifactTrustedChecksum = "foobar";
+        ArtifactResult artifactResult = createArtifactResult( 
artifactWithTrustedChecksum );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+
+        subject.postProcess( session, Collections.singletonList( 
artifactResult ) );
+        assertThat( artifactResult.isResolved(), equalTo( false ) );
+        assertThat( artifactResult.getExceptions(), not( empty() ) );
+        assertThat( artifactResult.getExceptions().get( 0 ).getMessage(),
+                containsString( "trusted checksum mismatch" ) );
+        assertThat( artifactResult.getExceptions().get( 0 ).getMessage(),
+                containsString( TRUSTED_SOURCE_NAME + "=" + 
artifactTrustedChecksum ) );
+    }
+
+    @Test
+    public void recordCalculatedChecksum()
+    {
+        AtomicReference<String> recordedChecksum = new AtomicReference<>(null);
+        this.trustedChecksumsWriter = new Writer()
+        {
+            @Override
+            public void addTrustedArtifactChecksums( Artifact artifact, 
ArtifactRepository artifactRepository,
+                                                     
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
+                                                     Map<String, String> 
trustedArtifactChecksums )
+            {
+                recordedChecksum.set( trustedArtifactChecksums.get( 
checksumAlgorithmFactory.getName() ) );
+            }
+
+            @Override
+            public void close()
+            {
+                // nop
+            }
+        };
+        session.setConfigProperty( 
"aether.artifactResolver.postProcessor.trusted-checksums.record",
+                Boolean.TRUE.toString() );
+        ArtifactResult artifactResult = createArtifactResult( 
artifactWithTrustedChecksum );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+
+        subject.postProcess( session, Collections.singletonList( 
artifactResult ) );
+        assertThat( artifactResult.isResolved(), equalTo( true ) );
+
+        String checksum = recordedChecksum.get();
+        assertThat( checksum, notNullValue() );
+        assertThat( checksum, equalTo( artifactTrustedChecksum ) );
+    }
+}
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/checksums/TrustedChecksumsSource.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/checksums/TrustedChecksumsSource.java
index 47e220ae..5d36a585 100644
--- 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/checksums/TrustedChecksumsSource.java
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/checksums/TrustedChecksumsSource.java
@@ -19,6 +19,8 @@ package org.eclipse.aether.spi.checksums;
  * under the License.
  */
 
+import java.io.Closeable;
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
@@ -39,16 +41,42 @@ import 
org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
 public interface TrustedChecksumsSource
 {
     /**
-     * May return the trusted checksums (for given artifact) from trusted 
source, or {@code null}.
+     * May return the trusted checksums (for given artifact) from trusted 
source, or {@code null} if not enabled.
+     * Enabled trusted checksum source SHOULD return non-null (empty map) 
result, when it has no data for given
+     * artifact. Empty map means in this case "no information", but how that 
case is interpreted depends on consumer
+     * for trusted checksums.
      *
      * @param session                    The repository system session, never 
{@code null}.
      * @param artifact                   The artifact we want checksums for, 
never {@code null}.
      * @param artifactRepository         The origin repository: local, 
workspace, remote repository, never {@code null}.
      * @param checksumAlgorithmFactories The checksum algorithms that are 
expected, never {@code null}.
-     * @return Map of expected checksums, or {@code null}.
+     * @return Map of expected checksums, or {@code null} if not enabled.
      */
     Map<String, String> getTrustedArtifactChecksums( RepositorySystemSession 
session,
                                                      Artifact artifact,
                                                      ArtifactRepository 
artifactRepository,
                                                      
List<ChecksumAlgorithmFactory> checksumAlgorithmFactories );
+
+    /**
+     * A writer that is able to write/add trusted checksums to this 
implementation. Should be treated as a resource
+     * as underlying implementation may rely on being closed after not used 
anymore.
+     */
+    interface Writer extends Closeable
+    {
+        /**
+         * Performs whatever implementation requires to "set" 
(write/add/append) given map of trusted checksums.
+         * The passed in list of checksum algorithm factories and the map must 
have equal size and mapping must
+         * contain all algorithm names in list.
+         */
+        void addTrustedArtifactChecksums( Artifact artifact,
+                                          ArtifactRepository 
artifactRepository,
+                                          List<ChecksumAlgorithmFactory> 
checksumAlgorithmFactories,
+                                          Map<String, String> 
trustedArtifactChecksums ) throws IOException;
+    }
+
+    /**
+     * Some trusted checksums sources may implement this optional method: 
ability to write/add checksums to them.
+     * If source does not support this feature, method should return {@code 
null}.
+     */
+    Writer getTrustedArtifactChecksumsWriter( RepositorySystemSession session 
);
 }
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java
index 4b958a6d..5d89d40d 100644
--- 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ChecksumAlgorithmFactorySelector.java
@@ -20,6 +20,9 @@ package org.eclipse.aether.spi.connector.checksum;
  */
 
 import java.util.Collection;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
 
 /**
  * Component performing selection of {@link ChecksumAlgorithmFactory} based on 
known factory names.
@@ -35,6 +38,19 @@ public interface ChecksumAlgorithmFactorySelector
      */
     ChecksumAlgorithmFactory select( String algorithmName );
 
+    /**
+     * Returns list of factories for given algorithm names in order as 
collection is ordered, or throws if algorithm
+     * not supported.
+     *
+     * @throws IllegalArgumentException if asked algorithm name is not 
supported.
+     * @throws NullPointerException if passed in list of names is {@code null}.
+     * @since TBD
+     */
+    default List<ChecksumAlgorithmFactory> select( Collection<String> 
algorithmNames )
+    {
+        return algorithmNames.stream().map( this::select ).collect( toList() );
+    }
+
     /**
      * Returns a collection of supported algorithms. This set represents ALL 
the algorithms supported by Resolver,
      * and is NOT in any relation to given repository layout used checksums, 
returned by method {@link
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ProvidedChecksumsSource.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ProvidedChecksumsSource.java
index 3ba83e47..809fedee 100644
--- 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ProvidedChecksumsSource.java
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/checksum/ProvidedChecksumsSource.java
@@ -34,10 +34,16 @@ import org.eclipse.aether.spi.connector.ArtifactDownload;
 public interface ProvidedChecksumsSource
 {
     /**
-     * May return the provided checksums (for given artifact transfer) from 
trusted source other than remote
-     * repository, or {@code null}.
+     * May return the provided checksums (for given artifact transfer) from 
source other than remote repository, or
+     * {@code null} if it have no checksums available for given transfer. 
Provided checksums are "opt-in" for
+     * transfer, in a way IF they are available upfront, they will be enforced 
according to checksum policy
+     * in effect. Otherwise, provided checksum verification is completely left 
out.
+     * <p>
+     * For enabled provided checksum source is completely acceptable to return 
{@code null} values, as that carries
+     * the meaning "nothing to add here", as there are no checksums to be 
provided upfront transfer. Semantically, this
+     * is equivalent to returning empty map, but signals the intent better.
      *
-     * @param transfer The transfer that is about to be executed.
+     * @param transfer                   The transfer that is about to be 
executed.
      * @param checksumAlgorithmFactories The checksum algorithms that are 
expected.
      * @return Map of expected checksums, or {@code null}.
      */
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/resolution/ArtifactResolverPostProcessor.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/resolution/ArtifactResolverPostProcessor.java
new file mode 100644
index 00000000..990358b9
--- /dev/null
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/resolution/ArtifactResolverPostProcessor.java
@@ -0,0 +1,47 @@
+package org.eclipse.aether.spi.resolution;
+
+/*
+ * 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 org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.resolution.ArtifactResult;
+
+/**
+ * Artifact resolver post-resolution processor component, is able to hook into 
resolver and post-process the resolved
+ * artifact results, if needed even produce resolution failure. It will always 
be invoked (even when failure is about
+ * to happen), so detecting these cases are left to post processor 
implementations.
+ *
+ * @since TBD
+ */
+public interface ArtifactResolverPostProcessor
+{
+    /**
+     * Receives resolver results just before it would return it to caller. Is 
able to generate "resolution failure"
+     * by augmenting passed in {@link ArtifactResult}s (artifacts should be 
"unresolved" and exceptions added).
+     * <p>
+     * Implementations must be aware that the passed in list of {@link 
ArtifactResult}s may have failed resolutions,
+     * best to check that using {@link ArtifactResult#isResolved()} method.
+     * <p>
+     * The implementations must be aware that this call may be "hot", so it 
directly affects the performance of
+     * resolver in general.
+     */
+    void postProcess( RepositorySystemSession session, List<ArtifactResult> 
artifactResults );
+}
diff --git 
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java 
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java
index df1ba3e3..ac4f38be 100644
--- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java
+++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/ConfigUtils.java
@@ -20,6 +20,7 @@ package org.eclipse.aether.util;
  */
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -27,6 +28,8 @@ import java.util.Map;
 
 import org.eclipse.aether.RepositorySystemSession;
 
+import static java.util.stream.Collectors.toList;
+
 /**
  * A utility class to read configuration properties from a repository system 
session.
  * 
@@ -395,4 +398,31 @@ public final class ConfigUtils
         return getMap( session.getConfigProperties(), defaultValue, keys );
     }
 
+    /**
+     * Utility method to parse configuration string that contains comma 
separated list of names into
+     * {@link List<String>}, never returns {@code null}.
+     *
+     * @since TBD
+     */
+    public static List<String> parseCommaSeparatedNames( String 
commaSeparatedNames )
+    {
+        if ( commaSeparatedNames == null || 
commaSeparatedNames.trim().isEmpty() )
+        {
+            return Collections.emptyList();
+        }
+        return Arrays.stream( commaSeparatedNames.split( "," ) )
+                .filter( s -> s != null && !s.trim().isEmpty() )
+                .collect( toList() );
+    }
+
+    /**
+     * Utility method to parse configuration string that contains comma 
separated list of names into
+     * {@link List<String>} with unique elements (duplicates, if any, are 
discarded), never returns {@code null}.
+     *
+     * @since TBD
+     */
+    public static List<String> parseCommaSeparatedUniqueNames( String 
commaSeparatedNames )
+    {
+        return parseCommaSeparatedNames( commaSeparatedNames 
).stream().distinct().collect( toList() );
+    }
 }

Reply via email to