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 bf07bfbf [MRESOLVER-569] Pass on exceptions (#678)
bf07bfbf is described below

commit bf07bfbfecf540874149ee6e50cc07707c9eaca2
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Tue Apr 8 14:36:32 2025 +0200

    [MRESOLVER-569] Pass on exceptions (#678)
    
    Pass on received exceptions as suppressed. Resolver was conceived when no 
suppressed exceptions existed and does not make use of them at all. This PR 
leaves existing "cause" management intact, but passes the whole tree (even for 
notorious `ArtifactResolutionException`) the full tree as suppressed exceptions.
    
    This implies that old code can still make use of existing logic to create 
the (incomplete) error messages, while new code can get all the needed 
information by walking and including suppressed exceptions as well. Logic is: 
if there is suppressed (and is same instance as cause or cause is null), then 
the given cause, if any, is "just the first out of many suppressed", hence 
suppressed "prevails" the cause. In that case code needs to "follow" suppressed 
ones.
    
    Example: created a POM with non existent dependency, and added 3 extra 
reposes (so 4 in total with Central):
    https://gist.github.com/cstamas/2d770f073a0260debb68dcc7ab3a7799
    The whole history is now in exceptions.
    
    ---
    
    https://issues.apache.org/jira/browse/MRESOLVER-569
---
 .../collection/DependencyCollectionException.java  | 18 +++++++--
 .../resolution/ArtifactDescriptorException.java    | 18 +++++++--
 .../resolution/ArtifactResolutionException.java    | 46 +++++++++++++++++++---
 .../VersionRangeResolutionException.java           | 16 +++++++-
 .../resolution/VersionResolutionException.java     | 18 +++++++--
 5 files changed, 100 insertions(+), 16 deletions(-)

diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyCollectionException.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyCollectionException.java
index 397219ee..36733ed7 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyCollectionException.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/collection/DependencyCollectionException.java
@@ -30,27 +30,36 @@ public class DependencyCollectionException extends 
RepositoryException {
 
     /**
      * Creates a new exception with the specified result.
+     * Cause will be first selected exception from result, if applicable. All 
exceptions are added as suppressed as well.
      *
      * @param result The collection result at the point the exception 
occurred, may be {@code null}.
      */
     public DependencyCollectionException(CollectResult result) {
-        super("Failed to collect dependencies for " + getSource(result), 
getCause(result));
+        super("Failed to collect dependencies for " + getSource(result), 
getFirstCause(result));
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
     /**
      * Creates a new exception with the specified result and detail message.
+     * Cause will be first selected exception from result, if applicable. All 
exceptions are added as suppressed as well.
      *
      * @param result The collection result at the point the exception 
occurred, may be {@code null}.
      * @param message The detail message, may be {@code null}.
      */
     public DependencyCollectionException(CollectResult result, String message) 
{
-        super(message, getCause(result));
+        super(message, getFirstCause(result));
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
     /**
      * Creates a new exception with the specified result, detail message and 
cause.
+     * All exceptions are added as suppressed as well.
      *
      * @param result The collection result at the point the exception 
occurred, may be {@code null}.
      * @param message The detail message, may be {@code null}.
@@ -58,6 +67,9 @@ public class DependencyCollectionException extends 
RepositoryException {
      */
     public DependencyCollectionException(CollectResult result, String message, 
Throwable cause) {
         super(message, cause);
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
@@ -87,7 +99,7 @@ public class DependencyCollectionException extends 
RepositoryException {
         return request.getDependencies().toString();
     }
 
-    private static Throwable getCause(CollectResult result) {
+    private static Throwable getFirstCause(CollectResult result) {
         Throwable cause = null;
         if (result != null && !result.getExceptions().isEmpty()) {
             cause = result.getExceptions().get(0);
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactDescriptorException.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactDescriptorException.java
index bda7ac59..29bab6af 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactDescriptorException.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactDescriptorException.java
@@ -29,6 +29,7 @@ public class ArtifactDescriptorException extends 
RepositoryException {
 
     /**
      * Creates a new exception with the specified result.
+     * Cause will be first selected exception from result, if applicable. All 
exceptions are added as suppressed as well.
      *
      * @param result The descriptor result at the point the exception 
occurred, may be {@code null}.
      */
@@ -36,23 +37,31 @@ public class ArtifactDescriptorException extends 
RepositoryException {
         super(
                 "Failed to read artifact descriptor"
                         + (result != null ? " for " + 
result.getRequest().getArtifact() : ""),
-                getCause(result));
+                getFirstCause(result));
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
     /**
      * Creates a new exception with the specified result and detail message.
+     * Cause will be first selected exception from result, if applicable. All 
exceptions are added as suppressed as well.
      *
      * @param result The descriptor result at the point the exception 
occurred, may be {@code null}.
      * @param message The detail message, may be {@code null}.
      */
     public ArtifactDescriptorException(ArtifactDescriptorResult result, String 
message) {
-        super(message, getCause(result));
+        super(message, getFirstCause(result));
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
     /**
      * Creates a new exception with the specified result, detail message and 
cause.
+     * All exceptions are added as suppressed as well.
      *
      * @param result The descriptor result at the point the exception 
occurred, may be {@code null}.
      * @param message The detail message, may be {@code null}.
@@ -60,6 +69,9 @@ public class ArtifactDescriptorException extends 
RepositoryException {
      */
     public ArtifactDescriptorException(ArtifactDescriptorResult result, String 
message, Throwable cause) {
         super(message, cause);
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
@@ -73,7 +85,7 @@ public class ArtifactDescriptorException extends 
RepositoryException {
         return result;
     }
 
-    private static Throwable getCause(ArtifactDescriptorResult result) {
+    private static Throwable getFirstCause(ArtifactDescriptorResult result) {
         Throwable cause = null;
         if (result != null && !result.getExceptions().isEmpty()) {
             cause = result.getExceptions().get(0);
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactResolutionException.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactResolutionException.java
index 0ec971b2..b91a6b7a 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactResolutionException.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/ArtifactResolutionException.java
@@ -18,10 +18,13 @@
  */
 package org.eclipse.aether.resolution;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.eclipse.aether.RepositoryException;
+import org.eclipse.aether.repository.ArtifactRepository;
 import org.eclipse.aether.repository.LocalArtifactResult;
 import org.eclipse.aether.transfer.ArtifactFilteredOutException;
 import org.eclipse.aether.transfer.ArtifactNotFoundException;
@@ -39,7 +42,10 @@ public class ArtifactResolutionException extends 
RepositoryException {
      * @param results The resolution results at the point the exception 
occurred, may be {@code null}.
      */
     public ArtifactResolutionException(List<ArtifactResult> results) {
-        super(getMessage(results), getCause(results));
+        super(getSmartMessage(results), getSmartCause(results));
+        if (results != null) {
+            getSuppressed(results).forEach(this::addSuppressed);
+        }
         this.results = results != null ? results : Collections.emptyList();
     }
 
@@ -50,7 +56,10 @@ public class ArtifactResolutionException extends 
RepositoryException {
      * @param message The detail message, may be {@code null}.
      */
     public ArtifactResolutionException(List<ArtifactResult> results, String 
message) {
-        super(message, getCause(results));
+        super(message, getSmartCause(results));
+        if (results != null) {
+            getSuppressed(results).forEach(this::addSuppressed);
+        }
         this.results = results != null ? results : Collections.emptyList();
     }
 
@@ -63,6 +72,9 @@ public class ArtifactResolutionException extends 
RepositoryException {
      */
     public ArtifactResolutionException(List<ArtifactResult> results, String 
message, Throwable cause) {
         super(message, cause);
+        if (results != null) {
+            getSuppressed(results).forEach(this::addSuppressed);
+        }
         this.results = results != null ? results : Collections.emptyList();
     }
 
@@ -86,7 +98,7 @@ public class ArtifactResolutionException extends 
RepositoryException {
         return (results != null && !results.isEmpty()) ? results.get(0) : null;
     }
 
-    private static String getMessage(List<? extends ArtifactResult> results) {
+    private static String getSmartMessage(List<? extends ArtifactResult> 
results) {
         if (results == null) {
             return null;
         }
@@ -116,7 +128,7 @@ public class ArtifactResolutionException extends 
RepositoryException {
             }
         }
 
-        Throwable cause = getCause(results);
+        Throwable cause = getSmartCause(results);
         if (cause != null) {
             buffer.append(": ").append(cause.getMessage());
         }
@@ -129,7 +141,7 @@ public class ArtifactResolutionException extends 
RepositoryException {
      * and probably many other code relies on it, so is left in place, but 
client code should use {@link #getResults()}
      * and {@link ArtifactResult#getMappedExceptions()} methods to build more 
appropriate error messages.
      */
-    private static Throwable getCause(List<? extends ArtifactResult> results) {
+    private static Throwable getSmartCause(List<? extends ArtifactResult> 
results) {
         if (results == null) {
             return null;
         }
@@ -158,4 +170,28 @@ public class ArtifactResolutionException extends 
RepositoryException {
         }
         return null;
     }
+
+    /**
+     * Builds a forest of exceptions to be used as suppressed, and it will 
contain the whole forest of exceptions per
+     * repository.
+     */
+    private static List<Throwable> getSuppressed(List<? extends 
ArtifactResult> results) {
+        ArrayList<Throwable> result = new ArrayList<>(results.size());
+        for (ArtifactResult artifactResult : results) {
+            if (!artifactResult.isResolved()) {
+                ArtifactResolutionException root = new 
ArtifactResolutionException(
+                        null,
+                        "Failed to resolve artifact "
+                                + artifactResult.getRequest().getArtifact());
+                for (Map.Entry<ArtifactRepository, List<Exception>> entry :
+                        artifactResult.getMappedExceptions().entrySet()) {
+                    for (Exception e : entry.getValue()) {
+                        root.addSuppressed(e);
+                    }
+                }
+                result.add(root);
+            }
+        }
+        return result;
+    }
 }
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionRangeResolutionException.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionRangeResolutionException.java
index 821b6634..74450fc0 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionRangeResolutionException.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionRangeResolutionException.java
@@ -29,11 +29,15 @@ public class VersionRangeResolutionException extends 
RepositoryException {
 
     /**
      * Creates a new exception with the specified result.
+     * Cause will be first selected exception from result, if applicable. All 
exceptions are added as suppressed as well.
      *
      * @param result The version range result at the point the exception 
occurred, may be {@code null}.
      */
     public VersionRangeResolutionException(VersionRangeResult result) {
-        super(getMessage(result), getCause(result));
+        super(getMessage(result), getFirstCause(result));
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
@@ -50,7 +54,7 @@ public class VersionRangeResolutionException extends 
RepositoryException {
         return buffer.toString();
     }
 
-    private static Throwable getCause(VersionRangeResult result) {
+    private static Throwable getFirstCause(VersionRangeResult result) {
         Throwable cause = null;
         if (result != null && !result.getExceptions().isEmpty()) {
             cause = result.getExceptions().get(0);
@@ -60,17 +64,22 @@ public class VersionRangeResolutionException extends 
RepositoryException {
 
     /**
      * Creates a new exception with the specified result and detail message.
+     * All exceptions are added as suppressed as well.
      *
      * @param result The version range result at the point the exception 
occurred, may be {@code null}.
      * @param message The detail message, may be {@code null}.
      */
     public VersionRangeResolutionException(VersionRangeResult result, String 
message) {
         super(message);
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
     /**
      * Creates a new exception with the specified result, detail message and 
cause.
+     * All exceptions are added as suppressed as well.
      *
      * @param result The version range result at the point the exception 
occurred, may be {@code null}.
      * @param message The detail message, may be {@code null}.
@@ -78,6 +87,9 @@ public class VersionRangeResolutionException extends 
RepositoryException {
      */
     public VersionRangeResolutionException(VersionRangeResult result, String 
message, Throwable cause) {
         super(message, cause);
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionResolutionException.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionResolutionException.java
index 538c8c36..95dfa458 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionResolutionException.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/resolution/VersionResolutionException.java
@@ -29,11 +29,15 @@ public class VersionResolutionException extends 
RepositoryException {
 
     /**
      * Creates a new exception with the specified result.
+     * Cause will be first selected exception from result, if applicable. All 
exceptions are added as suppressed as well.
      *
      * @param result The version result at the point the exception occurred, 
may be {@code null}.
      */
     public VersionResolutionException(VersionResult result) {
-        super(getMessage(result), getCause(result));
+        super(getMessage(result), getFirstCause(result));
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
@@ -50,7 +54,7 @@ public class VersionResolutionException extends 
RepositoryException {
         return buffer.toString();
     }
 
-    private static Throwable getCause(VersionResult result) {
+    private static Throwable getFirstCause(VersionResult result) {
         Throwable cause = null;
         if (result != null && !result.getExceptions().isEmpty()) {
             cause = result.getExceptions().get(0);
@@ -60,17 +64,22 @@ public class VersionResolutionException extends 
RepositoryException {
 
     /**
      * Creates a new exception with the specified result and detail message.
+     * Cause will be first selected exception from result, if applicable. All 
exceptions are added as suppressed as well.
      *
      * @param result The version result at the point the exception occurred, 
may be {@code null}.
      * @param message The detail message, may be {@code null}.
      */
     public VersionResolutionException(VersionResult result, String message) {
-        super(message, getCause(result));
+        super(message, getFirstCause(result));
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 
     /**
      * Creates a new exception with the specified result, detail message and 
cause.
+     * All exceptions are added as suppressed as well.
      *
      * @param result The version result at the point the exception occurred, 
may be {@code null}.
      * @param message The detail message, may be {@code null}.
@@ -78,6 +87,9 @@ public class VersionResolutionException extends 
RepositoryException {
      */
     public VersionResolutionException(VersionResult result, String message, 
Throwable cause) {
         super(message, cause);
+        if (result != null) {
+            result.getExceptions().forEach(this::addSuppressed);
+        }
         this.result = result;
     }
 

Reply via email to