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 6caeda24 [MRESOLVER-687] Validator SPI (#671)
6caeda24 is described below

commit 6caeda24f14a04fa7da2d19965cf04a526e9d1b7
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Wed Mar 19 13:03:31 2025 +0100

    [MRESOLVER-687] Validator SPI (#671)
    
    A new extension point that validates values coming into main entry point, 
the repository system. Provides ability to "hook in" and validate values. 
Resolver by itself does NOT provide any validator, it is up to integrating app 
to provide one, if wants.
    
    ---
    
    https://issues.apache.org/jira/browse/MRESOLVER-687
---
 .../aether/impl/RepositorySystemValidator.java     |  61 ++++
 .../internal/impl/DefaultRepositorySystem.java     |  41 +--
 .../impl/DefaultRepositorySystemValidator.java     | 318 +++++++++++++++++++++
 .../internal/impl/DefaultRepositorySystemTest.java |   3 +-
 .../eclipse/aether/spi/validator/Validator.java    |  72 +++++
 .../aether/spi/validator/ValidatorFactory.java     |  36 +++
 .../eclipse/aether/spi/validator/package-info.java |  43 +++
 .../aether/supplier/RepositorySystemSupplier.java  |  36 ++-
 .../aether/supplier/RepositorySystemSupplier.java  |  36 ++-
 9 files changed, 626 insertions(+), 20 deletions(-)

diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemValidator.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemValidator.java
new file mode 100644
index 00000000..030f677b
--- /dev/null
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RepositorySystemValidator.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.impl;
+
+import java.util.Collection;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.deployment.DeployRequest;
+import org.eclipse.aether.installation.InstallRequest;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.MetadataRequest;
+import org.eclipse.aether.resolution.VersionRangeRequest;
+import org.eclipse.aether.resolution.VersionRequest;
+
+/**
+ * Validator used by {@link org.eclipse.aether.RepositorySystem}. All method 
throw {@link IllegalArgumentException}.
+ */
+public interface RepositorySystemValidator {
+    void validateVersionRequest(RepositorySystemSession session, 
VersionRequest request);
+
+    void validateVersionRangeRequest(RepositorySystemSession session, 
VersionRangeRequest request);
+
+    void validateArtifactDescriptorRequest(RepositorySystemSession session, 
ArtifactDescriptorRequest request);
+
+    void validateArtifactRequests(RepositorySystemSession session, 
Collection<? extends ArtifactRequest> requests);
+
+    void validateMetadataRequests(RepositorySystemSession session, 
Collection<? extends MetadataRequest> requests);
+
+    void validateCollectRequest(RepositorySystemSession session, 
CollectRequest request);
+
+    void validateDependencyRequest(RepositorySystemSession session, 
DependencyRequest request);
+
+    void validateInstallRequest(RepositorySystemSession session, 
InstallRequest request);
+
+    void validateDeployRequest(RepositorySystemSession session, DeployRequest 
request);
+
+    void validateLocalRepositories(RepositorySystemSession session, 
Collection<LocalRepository> repositories);
+
+    void validateRemoteRepositories(RepositorySystemSession session, 
Collection<RemoteRepository> repositories);
+}
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 11ea54d1..947e079d 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
@@ -25,6 +25,7 @@ import javax.inject.Singleton;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -57,6 +58,7 @@ import org.eclipse.aether.impl.LocalRepositoryProvider;
 import org.eclipse.aether.impl.MetadataResolver;
 import org.eclipse.aether.impl.RemoteRepositoryManager;
 import org.eclipse.aether.impl.RepositorySystemLifecycle;
+import org.eclipse.aether.impl.RepositorySystemValidator;
 import org.eclipse.aether.impl.VersionRangeResolver;
 import org.eclipse.aether.impl.VersionResolver;
 import org.eclipse.aether.installation.InstallRequest;
@@ -133,7 +135,9 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
 
     private final RepositorySystemLifecycle repositorySystemLifecycle;
 
-    protected final Map<String, ArtifactDecoratorFactory> 
artifactDecoratorFactories;
+    private final Map<String, ArtifactDecoratorFactory> 
artifactDecoratorFactories;
+
+    private final RepositorySystemValidator repositorySystemValidator;
 
     @SuppressWarnings("checkstyle:parameternumber")
     @Inject
@@ -150,7 +154,8 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             SyncContextFactory syncContextFactory,
             RemoteRepositoryManager remoteRepositoryManager,
             RepositorySystemLifecycle repositorySystemLifecycle,
-            Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories) {
+            Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories,
+            RepositorySystemValidator repositorySystemValidator) {
         this.shutdown = new AtomicBoolean(false);
         this.sessionIdCounter = new AtomicInteger(0);
         this.versionResolver = requireNonNull(versionResolver, "version 
resolver cannot be null");
@@ -171,6 +176,8 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
                 requireNonNull(repositorySystemLifecycle, "repository system 
lifecycle cannot be null");
         this.artifactDecoratorFactories =
                 requireNonNull(artifactDecoratorFactories, "artifact decorator 
factories cannot be null");
+        this.repositorySystemValidator =
+                requireNonNull(repositorySystemValidator, "repository system 
validator cannot be null");
     }
 
     @Override
@@ -178,7 +185,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             throws VersionResolutionException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateVersionRequest(session, request);
         return versionResolver.resolveVersion(session, request);
     }
 
@@ -187,7 +194,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             throws VersionRangeResolutionException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateVersionRangeRequest(session, 
request);
         return versionRangeResolver.resolveVersionRange(session, request);
     }
 
@@ -196,7 +203,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             RepositorySystemSession session, ArtifactDescriptorRequest 
request) throws ArtifactDescriptorException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateArtifactDescriptorRequest(session, 
request);
         ArtifactDescriptorResult descriptorResult = 
artifactDescriptorReader.readArtifactDescriptor(session, request);
         for (ArtifactDecorator decorator : 
Utils.getArtifactDecorators(session, artifactDecoratorFactories)) {
             
descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult));
@@ -209,7 +216,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             throws ArtifactResolutionException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateArtifactRequests(session, 
Collections.singleton(request));
         return artifactResolver.resolveArtifact(session, request);
     }
 
@@ -219,7 +226,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             throws ArtifactResolutionException {
         validateSession(session);
         requireNonNull(requests, "requests cannot be null");
-
+        repositorySystemValidator.validateArtifactRequests(session, requests);
         return artifactResolver.resolveArtifacts(session, requests);
     }
 
@@ -228,7 +235,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             RepositorySystemSession session, Collection<? extends 
MetadataRequest> requests) {
         validateSession(session);
         requireNonNull(requests, "requests cannot be null");
-
+        repositorySystemValidator.validateMetadataRequests(session, requests);
         return metadataResolver.resolveMetadata(session, requests);
     }
 
@@ -237,7 +244,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             throws DependencyCollectionException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateCollectRequest(session, request);
         return dependencyCollector.collectDependencies(session, request);
     }
 
@@ -246,7 +253,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             throws DependencyResolutionException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateDependencyRequest(session, request);
         RequestTrace trace = RequestTrace.newChild(request.getTrace(), 
request);
 
         DependencyResult result = new DependencyResult(request);
@@ -360,7 +367,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
     public InstallResult install(RepositorySystemSession session, 
InstallRequest request) throws InstallationException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateInstallRequest(session, request);
         return installer.install(session, request);
     }
 
@@ -368,7 +375,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
     public DeployResult deploy(RepositorySystemSession session, DeployRequest 
request) throws DeploymentException {
         validateSession(session);
         requireNonNull(request, "request cannot be null");
-
+        repositorySystemValidator.validateDeployRequest(session, request);
         return deployer.deploy(session, request);
     }
 
@@ -378,7 +385,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
         requireNonNull(session, "session cannot be null");
         requireNonNull(localRepository, "localRepository cannot be null");
         validateSystem();
-
+        repositorySystemValidator.validateLocalRepositories(session, 
Collections.singleton(localRepository));
         return createLocalRepositoryManager(session, localRepository);
     }
 
@@ -388,7 +395,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
         requireNonNull(session, "session cannot be null");
         requireNonNull(localRepositories, "localRepositories cannot be null");
         validateSystem();
-
+        repositorySystemValidator.validateLocalRepositories(session, 
Arrays.asList(localRepositories));
         return createLocalRepositoryManager(session, 
Arrays.asList(localRepositories));
     }
 
@@ -398,7 +405,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
         requireNonNull(session, "session cannot be null");
         requireNonNull(localRepositories, "localRepositories cannot be null");
         validateSystem();
-
+        repositorySystemValidator.validateLocalRepositories(session, 
localRepositories);
         return createLocalRepositoryManager(session, localRepositories);
     }
 
@@ -437,7 +444,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
             RepositorySystemSession session, List<RemoteRepository> 
repositories) {
         validateSession(session);
         validateRepositories(repositories);
-
+        repositorySystemValidator.validateRemoteRepositories(session, 
repositories);
         repositories = remoteRepositoryManager.aggregateRepositories(session, 
new ArrayList<>(), repositories, true);
         return repositories;
     }
@@ -446,7 +453,7 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
     public RemoteRepository newDeploymentRepository(RepositorySystemSession 
session, RemoteRepository repository) {
         validateSession(session);
         requireNonNull(repository, "repository cannot be null");
-
+        repositorySystemValidator.validateRemoteRepositories(session, 
Collections.singletonList(repository));
         RemoteRepository.Builder builder = new 
RemoteRepository.Builder(repository);
         Authentication auth = 
session.getAuthenticationSelector().getAuthentication(repository);
         builder.setAuthentication(auth);
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidator.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidator.java
new file mode 100644
index 00000000..15c85f20
--- /dev/null
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemValidator.java
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.internal.impl;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.deployment.DeployRequest;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.impl.RepositorySystemValidator;
+import org.eclipse.aether.installation.InstallRequest;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.MetadataRequest;
+import org.eclipse.aether.resolution.VersionRangeRequest;
+import org.eclipse.aether.resolution.VersionRequest;
+import org.eclipse.aether.spi.validator.Validator;
+import org.eclipse.aether.spi.validator.ValidatorFactory;
+
+import static java.util.Objects.requireNonNull;
+
+@Singleton
+@Named
+public class DefaultRepositorySystemValidator implements 
RepositorySystemValidator {
+    private final List<ValidatorFactory> validatorFactories;
+
+    @Inject
+    public DefaultRepositorySystemValidator(List<ValidatorFactory> 
validatorFactories) {
+        this.validatorFactories = requireNonNull(validatorFactories, 
"validatorFactories cannot be null");
+    }
+
+    private void mayThrow(List<Exception> exceptions, String message) {
+        if (!exceptions.isEmpty()) {
+            IllegalArgumentException result = new 
IllegalArgumentException(message);
+            exceptions.forEach(result::addSuppressed);
+            throw result;
+        }
+    }
+
+    @Override
+    public void validateVersionRequest(RepositorySystemSession session, 
VersionRequest request) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            try {
+                validator.validateArtifact(request.getArtifact());
+            } catch (Exception e) {
+                exceptions.add(e);
+            }
+            for (RemoteRepository repository : request.getRepositories()) {
+                try {
+                    validator.validateRemoteRepository(repository);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid Version Request: " + request);
+    }
+
+    @Override
+    public void validateVersionRangeRequest(RepositorySystemSession session, 
VersionRangeRequest request) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            try {
+                validator.validateArtifact(request.getArtifact());
+            } catch (Exception e) {
+                exceptions.add(e);
+            }
+            for (RemoteRepository repository : request.getRepositories()) {
+                try {
+                    validator.validateRemoteRepository(repository);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid Version Range Request: " + request);
+    }
+
+    @Override
+    public void validateArtifactDescriptorRequest(RepositorySystemSession 
session, ArtifactDescriptorRequest request) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            try {
+                validator.validateArtifact(request.getArtifact());
+            } catch (Exception e) {
+                exceptions.add(e);
+            }
+            for (RemoteRepository repository : request.getRepositories()) {
+                try {
+                    validator.validateRemoteRepository(repository);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid Artifact Descriptor Request: " + 
request);
+    }
+
+    @Override
+    public void validateArtifactRequests(
+            RepositorySystemSession session, Collection<? extends 
ArtifactRequest> requests) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            for (ArtifactRequest request : requests) {
+                try {
+                    validator.validateArtifact(request.getArtifact());
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+                for (RemoteRepository repository : request.getRepositories()) {
+                    try {
+                        validator.validateRemoteRepository(repository);
+                    } catch (Exception e) {
+                        exceptions.add(e);
+                    }
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid Artifact Requests: " + requests);
+    }
+
+    @Override
+    public void validateMetadataRequests(
+            RepositorySystemSession session, Collection<? extends 
MetadataRequest> requests) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            for (MetadataRequest request : requests) {
+                try {
+                    validator.validateMetadata(request.getMetadata());
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+                try {
+                    if (request.getRepository() != null) {
+                        
validator.validateRemoteRepository(request.getRepository());
+                    }
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid Metadata Requests: " + requests);
+    }
+
+    @Override
+    public void validateCollectRequest(RepositorySystemSession session, 
CollectRequest request) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            if (request.getRootArtifact() != null) {
+                try {
+                    validator.validateArtifact(request.getRootArtifact());
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+            if (request.getRoot() != null) {
+                try {
+                    validator.validateDependency(request.getRoot());
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+            for (Dependency dependency : request.getDependencies()) {
+                try {
+                    validator.validateDependency(dependency);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+            for (Dependency managedDependency : 
request.getManagedDependencies()) {
+                try {
+                    validator.validateDependency(managedDependency);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+            for (RemoteRepository repository : request.getRepositories()) {
+                try {
+                    validator.validateRemoteRepository(repository);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid Collect Request: " + request);
+    }
+
+    @Override
+    public void validateDependencyRequest(RepositorySystemSession session, 
DependencyRequest request) {
+        if (request.getCollectRequest() != null) {
+            try {
+                validateCollectRequest(session, request.getCollectRequest());
+            } catch (Exception e) {
+                throw new IllegalArgumentException("Invalid Dependency 
Request: " + request, e);
+            }
+        }
+    }
+
+    @Override
+    public void validateInstallRequest(RepositorySystemSession session, 
InstallRequest request) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            for (Artifact artifact : request.getArtifacts()) {
+                try {
+                    validator.validateArtifact(artifact);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+            for (Metadata metadata : request.getMetadata()) {
+                try {
+                    validator.validateMetadata(metadata);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid Install Request: " + request);
+    }
+
+    @Override
+    public void validateDeployRequest(RepositorySystemSession session, 
DeployRequest request) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            for (Artifact artifact : request.getArtifacts()) {
+                try {
+                    validator.validateArtifact(artifact);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+            for (Metadata metadata : request.getMetadata()) {
+                try {
+                    validator.validateMetadata(metadata);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+            try {
+                if (request.getRepository() != null) {
+                    
validator.validateRemoteRepository(request.getRepository());
+                }
+            } catch (Exception e) {
+                exceptions.add(e);
+            }
+        }
+        mayThrow(exceptions, "Invalid Deploy Request: " + request);
+    }
+
+    @Override
+    public void validateLocalRepositories(RepositorySystemSession session, 
Collection<LocalRepository> repositories) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            for (LocalRepository repository : repositories) {
+                try {
+                    validator.validateLocalRepository(repository);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid LocalRepositories: " + repositories);
+    }
+
+    @Override
+    public void validateRemoteRepositories(RepositorySystemSession session, 
Collection<RemoteRepository> repositories) {
+        ArrayList<Exception> exceptions = new ArrayList<>();
+        for (ValidatorFactory factory : validatorFactories) {
+            Validator validator = factory.newInstance(session);
+            for (RemoteRepository repository : repositories) {
+                try {
+                    validator.validateRemoteRepository(repository);
+                } catch (Exception e) {
+                    exceptions.add(e);
+                }
+            }
+        }
+        mayThrow(exceptions, "Invalid RemoteRepositories: " + repositories);
+    }
+}
diff --git 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java
 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java
index aa554d49..08b75df3 100644
--- 
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java
+++ 
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemTest.java
@@ -66,7 +66,8 @@ public class DefaultRepositorySystemTest {
                 new DefaultRemoteRepositoryManager(
                         new DefaultUpdatePolicyAnalyzer(), new 
DefaultChecksumPolicyProvider()),
                 new DefaultRepositorySystemLifecycle(),
-                Collections.emptyMap());
+                Collections.emptyMap(),
+                new DefaultRepositorySystemValidator(Collections.emptyList()));
         session = TestUtils.newSession();
     }
 
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/Validator.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/Validator.java
new file mode 100644
index 00000000..b4175760
--- /dev/null
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/Validator.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.spi.validator;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.metadata.Metadata;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+
+/**
+ * A repository system main input validator; this validator is used in 
repository system "main entry methods".
+ *
+ * @since 2.0.8
+ */
+public interface Validator {
+    /**
+     * Validates artifact.
+     *
+     * @param artifact The artifact to validate, never {@code null}.
+     * @throws IllegalArgumentException if artifact is invalid.
+     */
+    default void validateArtifact(Artifact artifact) throws 
IllegalArgumentException {}
+
+    /**
+     * Validates metadata.
+     *
+     * @param metadata The metadata to validate, never {@code null}.
+     * @throws IllegalArgumentException if artifact is invalid.
+     */
+    default void validateMetadata(Metadata metadata) throws 
IllegalArgumentException {}
+
+    /**
+     * Validates dependency.
+     *
+     * @param dependency The dependency to validate, never {@code null}.
+     * @throws IllegalArgumentException if dependency is invalid.
+     */
+    default void validateDependency(Dependency dependency) throws 
IllegalArgumentException {}
+
+    /**
+     * Validates local repository.
+     *
+     * @param localRepository The local repository to validate, never {@code 
null}.
+     * @throws IllegalArgumentException if local repository is invalid.
+     */
+    default void validateLocalRepository(LocalRepository localRepository) 
throws IllegalArgumentException {}
+
+    /**
+     * Validates remote repository.
+     *
+     * @param remoteRepository The remote repository to validate, never {@code 
null}.
+     * @throws IllegalArgumentException if remote repository is invalid.
+     */
+    default void validateRemoteRepository(RemoteRepository remoteRepository) 
throws IllegalArgumentException {}
+}
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/ValidatorFactory.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/ValidatorFactory.java
new file mode 100644
index 00000000..23e59a8f
--- /dev/null
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/ValidatorFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.spi.validator;
+
+import org.eclipse.aether.RepositorySystemSession;
+
+/**
+ * A factory to create validators.
+ *
+ * @since 2.0.8
+ */
+public interface ValidatorFactory {
+    /**
+     * Creates a new validator for the session.
+     *
+     * @param session The repository system session from which to configure 
the validator, must not be {@code null}.
+     * @return The validator for the session, never {@code null}.
+     */
+    Validator newInstance(RepositorySystemSession session);
+}
diff --git 
a/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/package-info.java
 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/package-info.java
new file mode 100644
index 00000000..b90abbc2
--- /dev/null
+++ 
b/maven-resolver-spi/src/main/java/org/eclipse/aether/spi/validator/package-info.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+/**
+ * Validator SPI.
+ * <p>
+ * This package provides callback extension points, that are invoked
+ * from Repository System, that is "main entry point" to Resolver.
+ * Validator is invoked before artifact would be resolved, installed or 
deployed. Given
+ * Resolver treats all coordinate elements as opaque strings, this extension
+ * provides ability for integrating application for early detection of any
+ * unwanted operation or bug, like "leaks" of un-interpolated artifacts
+ * asked to be resolved/installed/deployed. Resolver itself have no
+ * notion of "interpolation" nor "placeholders", again, it handles
+ * all received coordinates as opaque string and uses them to build
+ * resource URIs according to layout, but still, it is 100% that
+ * un-interpolated value will result in "no artifact found" error
+ * in case of resolution, but it may be and usually is due user
+ * error like having a typo in some property in POM for example.
+ * <p>
+ * Resolver does NOT provide this component, and is fully operable
+ * without it. It is left to integrating apps to decide do they
+ * want to provide components like this or not.
+ *
+ * @since 2.0.8
+ */
+package org.eclipse.aether.spi.validator;
diff --git 
a/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
 
b/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
index a1616deb..851d3f8a 100644
--- 
a/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
+++ 
b/maven-resolver-supplier-mvn3/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
@@ -18,7 +18,9 @@
  */
 package org.eclipse.aether.supplier;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -50,6 +52,7 @@ import org.eclipse.aether.impl.RemoteRepositoryManager;
 import org.eclipse.aether.impl.RepositoryConnectorProvider;
 import org.eclipse.aether.impl.RepositoryEventDispatcher;
 import org.eclipse.aether.impl.RepositorySystemLifecycle;
+import org.eclipse.aether.impl.RepositorySystemValidator;
 import org.eclipse.aether.impl.UpdateCheckManager;
 import org.eclipse.aether.impl.UpdatePolicyAnalyzer;
 import org.eclipse.aether.impl.VersionRangeResolver;
@@ -72,6 +75,7 @@ import 
org.eclipse.aether.internal.impl.DefaultRepositoryEventDispatcher;
 import org.eclipse.aether.internal.impl.DefaultRepositoryLayoutProvider;
 import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
 import org.eclipse.aether.internal.impl.DefaultRepositorySystemLifecycle;
+import org.eclipse.aether.internal.impl.DefaultRepositorySystemValidator;
 import org.eclipse.aether.internal.impl.DefaultTrackingFileManager;
 import org.eclipse.aether.internal.impl.DefaultTransporterProvider;
 import org.eclipse.aether.internal.impl.DefaultUpdateCheckManager;
@@ -133,6 +137,7 @@ import org.eclipse.aether.spi.io.PathProcessor;
 import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
 import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
+import org.eclipse.aether.spi.validator.ValidatorFactory;
 import org.eclipse.aether.transport.apache.ApacheTransporterFactory;
 import org.eclipse.aether.transport.file.FileTransporterFactory;
 import org.eclipse.aether.util.version.GenericVersionScheme;
@@ -1031,6 +1036,34 @@ public class RepositorySystemSupplier implements 
Supplier<RepositorySystem> {
         return new DefaultModelCacheFactory();
     }
 
+    private List<ValidatorFactory> validatorFactories;
+
+    public final List<ValidatorFactory> getValidatorFactories() {
+        checkClosed();
+        if (validatorFactories == null) {
+            validatorFactories = createValidatorFactories();
+        }
+        return validatorFactories;
+    }
+
+    protected List<ValidatorFactory> createValidatorFactories() {
+        return new ArrayList<>();
+    }
+
+    private RepositorySystemValidator repositorySystemValidator;
+
+    public final RepositorySystemValidator getRepositorySystemValidator() {
+        checkClosed();
+        if (repositorySystemValidator == null) {
+            repositorySystemValidator = createRepositorySystemValidator();
+        }
+        return repositorySystemValidator;
+    }
+
+    protected RepositorySystemValidator createRepositorySystemValidator() {
+        return new DefaultRepositorySystemValidator(getValidatorFactories());
+    }
+
     private RepositorySystem repositorySystem;
 
     public final RepositorySystem getRepositorySystem() {
@@ -1055,7 +1088,8 @@ public class RepositorySystemSupplier implements 
Supplier<RepositorySystem> {
                 getSyncContextFactory(),
                 getRemoteRepositoryManager(),
                 getRepositorySystemLifecycle(),
-                getArtifactDecoratorFactories());
+                getArtifactDecoratorFactories(),
+                getRepositorySystemValidator());
     }
 
     @Override
diff --git 
a/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
 
b/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
index ca3f1ac6..220ca806 100644
--- 
a/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
+++ 
b/maven-resolver-supplier-mvn4/src/main/java/org/eclipse/aether/supplier/RepositorySystemSupplier.java
@@ -18,8 +18,10 @@
  */
 package org.eclipse.aether.supplier;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
@@ -54,6 +56,7 @@ import org.eclipse.aether.impl.RemoteRepositoryManager;
 import org.eclipse.aether.impl.RepositoryConnectorProvider;
 import org.eclipse.aether.impl.RepositoryEventDispatcher;
 import org.eclipse.aether.impl.RepositorySystemLifecycle;
+import org.eclipse.aether.impl.RepositorySystemValidator;
 import org.eclipse.aether.impl.UpdateCheckManager;
 import org.eclipse.aether.impl.UpdatePolicyAnalyzer;
 import org.eclipse.aether.impl.VersionRangeResolver;
@@ -76,6 +79,7 @@ import 
org.eclipse.aether.internal.impl.DefaultRepositoryEventDispatcher;
 import org.eclipse.aether.internal.impl.DefaultRepositoryLayoutProvider;
 import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
 import org.eclipse.aether.internal.impl.DefaultRepositorySystemLifecycle;
+import org.eclipse.aether.internal.impl.DefaultRepositorySystemValidator;
 import org.eclipse.aether.internal.impl.DefaultTrackingFileManager;
 import org.eclipse.aether.internal.impl.DefaultTransporterProvider;
 import org.eclipse.aether.internal.impl.DefaultUpdateCheckManager;
@@ -137,6 +141,7 @@ import org.eclipse.aether.spi.io.PathProcessor;
 import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory;
 import org.eclipse.aether.spi.resolution.ArtifactResolverPostProcessor;
 import org.eclipse.aether.spi.synccontext.SyncContextFactory;
+import org.eclipse.aether.spi.validator.ValidatorFactory;
 import org.eclipse.aether.transport.apache.ApacheTransporterFactory;
 import org.eclipse.aether.transport.file.FileTransporterFactory;
 import org.eclipse.aether.util.version.GenericVersionScheme;
@@ -1056,6 +1061,34 @@ public class RepositorySystemSupplier implements 
Supplier<RepositorySystem> {
         return new DefaultModelCacheFactory();
     }
 
+    private List<ValidatorFactory> validatorFactories;
+
+    public final List<ValidatorFactory> getValidatorFactories() {
+        checkClosed();
+        if (validatorFactories == null) {
+            validatorFactories = createValidatorFactories();
+        }
+        return validatorFactories;
+    }
+
+    protected List<ValidatorFactory> createValidatorFactories() {
+        return new ArrayList<>();
+    }
+
+    private RepositorySystemValidator repositorySystemValidator;
+
+    public final RepositorySystemValidator getRepositorySystemValidator() {
+        checkClosed();
+        if (repositorySystemValidator == null) {
+            repositorySystemValidator = createRepositorySystemValidator();
+        }
+        return repositorySystemValidator;
+    }
+
+    protected RepositorySystemValidator createRepositorySystemValidator() {
+        return new DefaultRepositorySystemValidator(getValidatorFactories());
+    }
+
     private RepositorySystem repositorySystem;
 
     public final RepositorySystem getRepositorySystem() {
@@ -1080,7 +1113,8 @@ public class RepositorySystemSupplier implements 
Supplier<RepositorySystem> {
                 getSyncContextFactory(),
                 getRemoteRepositoryManager(),
                 getRepositorySystemLifecycle(),
-                getArtifactDecoratorFactories());
+                getArtifactDecoratorFactories(),
+                getRepositorySystemValidator());
     }
 
     @Override


Reply via email to