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; }