From 84067f66034e5b9e0f9cfcb47af7d986df817625 Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Fri, 25 Oct 2024 15:41:00 +0100 Subject: [PATCH] Include error details in unchecked Java exception The checked GatewayException already has its printStackTrace augmented with any associated gRPC ErrorDetail elements. The same functionality is added to the equivalent unchecked GatewayRuntimeException. The two exceptions are intended to behave identically, with the unchecked variant to be used only where a checked exception cannot be thrown due to the standard APIs exposed. For example, the Iterator methods used when retrieving events. Signed-off-by: Mark S. Lewis --- java/pom.xml | 24 ++--- .../fabric/client/GatewayException.java | 31 ++----- .../client/GatewayRuntimeException.java | 33 +++++++ .../fabric/client/GrpcStackTracePrinter.java | 54 +++++++++++ .../client/CommonGatewayExceptionTest.java | 89 +++++++++++++++++++ .../fabric/client/GatewayExceptionTest.java | 79 +--------------- .../client/GatewayRuntimeExceptionTest.java | 15 ++++ 7 files changed, 208 insertions(+), 117 deletions(-) create mode 100644 java/src/main/java/org/hyperledger/fabric/client/GrpcStackTracePrinter.java create mode 100644 java/src/test/java/org/hyperledger/fabric/client/CommonGatewayExceptionTest.java create mode 100644 java/src/test/java/org/hyperledger/fabric/client/GatewayRuntimeExceptionTest.java diff --git a/java/pom.xml b/java/pom.xml index 0fd969e65..8295df594 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -40,7 +40,7 @@ 8 1.78.1 ${skipTests} - 7.6.0 + 7.7.0 @@ -55,7 +55,7 @@ org.junit junit-bom - 5.11.2 + 5.11.3 pom import @@ -206,8 +206,8 @@ true -Xlint - -Xlint:-options + + -Xlint:-options -Werror @@ -301,15 +301,6 @@ report-integration - - default-check - - check - - - - - @@ -453,13 +444,14 @@ ${skipUnitTests} - - + + - 2.47.0 + 2.50.0 false + ${project.basedir}/license-header.txt diff --git a/java/src/main/java/org/hyperledger/fabric/client/GatewayException.java b/java/src/main/java/org/hyperledger/fabric/client/GatewayException.java index 458b9bdcb..13f22cacc 100644 --- a/java/src/main/java/org/hyperledger/fabric/client/GatewayException.java +++ b/java/src/main/java/org/hyperledger/fabric/client/GatewayException.java @@ -7,7 +7,6 @@ package org.hyperledger.fabric.client; import io.grpc.StatusRuntimeException; -import java.io.CharArrayWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.util.List; @@ -19,6 +18,8 @@ * more of those nodes. In that case, the details will contain errors information from those nodes. */ public class GatewayException extends Exception { + // Ignore similarity with unchecked GatewayRuntimeException - CPD-OFF + private static final long serialVersionUID = 1L; private final transient GrpcStatus grpcStatus; @@ -63,9 +64,7 @@ public void printStackTrace() { */ @Override public void printStackTrace(final PrintStream out) { - PrintWriter writer = new PrintWriter(out); - printStackTrace(writer); - writer.flush(); + new GrpcStackTracePrinter(super::printStackTrace, grpcStatus).printStackTrace(out); } /** @@ -74,26 +73,8 @@ public void printStackTrace(final PrintStream out) { */ @Override public void printStackTrace(final PrintWriter out) { - CharArrayWriter message = new CharArrayWriter(); - - try (PrintWriter printer = new PrintWriter(message)) { - super.printStackTrace(printer); - } - - List details = getDetails(); - if (!details.isEmpty()) { - message.append("Error details:\n"); - for (ErrorDetail detail : details) { - message.append(" address: ") - .append(detail.getAddress()) - .append("; mspId: ") - .append(detail.getMspId()) - .append("; message: ") - .append(detail.getMessage()) - .append('\n'); - } - } - - out.print(message); + new GrpcStackTracePrinter(super::printStackTrace, grpcStatus).printStackTrace(out); } + + // CPD-ON } diff --git a/java/src/main/java/org/hyperledger/fabric/client/GatewayRuntimeException.java b/java/src/main/java/org/hyperledger/fabric/client/GatewayRuntimeException.java index 031dde79b..4a4a859ab 100644 --- a/java/src/main/java/org/hyperledger/fabric/client/GatewayRuntimeException.java +++ b/java/src/main/java/org/hyperledger/fabric/client/GatewayRuntimeException.java @@ -7,6 +7,8 @@ package org.hyperledger.fabric.client; import io.grpc.StatusRuntimeException; +import java.io.PrintStream; +import java.io.PrintWriter; import java.util.List; import org.hyperledger.fabric.protos.gateway.ErrorDetail; @@ -16,6 +18,8 @@ * more of those nodes. In that case, the details will contain errors information from those nodes. */ public class GatewayRuntimeException extends RuntimeException { + // Ignore similarity with checked GatewayException - CPD-OFF + private static final long serialVersionUID = 1L; private final transient GrpcStatus grpcStatus; @@ -44,4 +48,33 @@ public io.grpc.Status getStatus() { public List getDetails() { return grpcStatus.getDetails(); } + + /** + * {@inheritDoc} + * This implementation appends any gRPC error details to the stack trace. + */ + @Override + public void printStackTrace() { + printStackTrace(System.err); + } + + /** + * {@inheritDoc} + * This implementation appends any gRPC error details to the stack trace. + */ + @Override + public void printStackTrace(final PrintStream out) { + new GrpcStackTracePrinter(super::printStackTrace, grpcStatus).printStackTrace(out); + } + + /** + * {@inheritDoc} + * This implementation appends any gRPC error details to the stack trace. + */ + @Override + public void printStackTrace(final PrintWriter out) { + new GrpcStackTracePrinter(super::printStackTrace, grpcStatus).printStackTrace(out); + } + + // CPD-ON } diff --git a/java/src/main/java/org/hyperledger/fabric/client/GrpcStackTracePrinter.java b/java/src/main/java/org/hyperledger/fabric/client/GrpcStackTracePrinter.java new file mode 100644 index 000000000..6db12ddd8 --- /dev/null +++ b/java/src/main/java/org/hyperledger/fabric/client/GrpcStackTracePrinter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.hyperledger.fabric.client; + +import java.io.CharArrayWriter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.List; +import java.util.function.Consumer; +import org.hyperledger.fabric.protos.gateway.ErrorDetail; + +class GrpcStackTracePrinter { + private final Consumer printStackTraceFn; + private final GrpcStatus grpcStatus; + + public GrpcStackTracePrinter(final Consumer printStackTraceFn, final GrpcStatus grpcStatus) { + this.printStackTraceFn = printStackTraceFn; + this.grpcStatus = grpcStatus; + } + + public void printStackTrace(final PrintStream out) { + PrintWriter writer = new PrintWriter(out); + printStackTrace(writer); + writer.flush(); + } + + public void printStackTrace(final PrintWriter out) { + CharArrayWriter message = new CharArrayWriter(); + + try (PrintWriter printer = new PrintWriter(message)) { + printStackTraceFn.accept(printer); + } + + List details = grpcStatus.getDetails(); + if (!details.isEmpty()) { + message.append("Error details:\n"); + for (ErrorDetail detail : details) { + message.append(" address: ") + .append(detail.getAddress()) + .append("; mspId: ") + .append(detail.getMspId()) + .append("; message: ") + .append(detail.getMessage()) + .append('\n'); + } + } + + out.print(message); + } +} diff --git a/java/src/test/java/org/hyperledger/fabric/client/CommonGatewayExceptionTest.java b/java/src/test/java/org/hyperledger/fabric/client/CommonGatewayExceptionTest.java new file mode 100644 index 000000000..fa506943f --- /dev/null +++ b/java/src/test/java/org/hyperledger/fabric/client/CommonGatewayExceptionTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.hyperledger.fabric.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.protobuf.Any; +import com.google.rpc.Code; +import io.grpc.StatusRuntimeException; +import io.grpc.protobuf.StatusProto; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.hyperledger.fabric.protos.gateway.ErrorDetail; +import org.junit.jupiter.api.Test; + +abstract class CommonGatewayExceptionTest { + protected abstract Exception newInstance(StatusRuntimeException e); + + @Test + void error_details_are_printed() { + List details = Arrays.asList( + ErrorDetail.newBuilder() + .setAddress("ADDRESS1") + .setMspId("MSPID1") + .setMessage("MESSAGE1") + .build(), + ErrorDetail.newBuilder() + .setAddress("ADDRESS2") + .setMspId("MSPID2") + .setMessage("MESSAGE2") + .build()); + Exception e = newInstance(newStatusRuntimeException(Code.ABORTED, "STATUS_MESSAGE", details)); + + ByteArrayOutputStream actual = new ByteArrayOutputStream(); + try (PrintStream out = new PrintStream(actual)) { + e.printStackTrace(out); + } + + List expected = details.stream() + .flatMap(detail -> Stream.of(detail.getAddress(), detail.getMspId(), detail.getMessage())) + .collect(Collectors.toList()); + assertThat(actual.toString()).contains(expected); + } + + @Test + void message_from_StatusRuntimeException_is_printed() { + Exception e = newInstance(newStatusRuntimeException(Code.ABORTED, "STATUS_MESSAGE", Collections.emptyList())); + + ByteArrayOutputStream actual = new ByteArrayOutputStream(); + try (PrintStream out = new PrintStream(actual)) { + e.printStackTrace(out); + } + + String expected = e.getCause().getLocalizedMessage(); + assertThat(actual.toString()).contains(expected); + } + + @Test + void print_stream_passed_to_printStackTrace_not_closed() { + Exception e = newInstance(newStatusRuntimeException(Code.ABORTED, "", Collections.emptyList())); + + ByteArrayOutputStream actual = new ByteArrayOutputStream(); + try (PrintStream out = new PrintStream(actual)) { + e.printStackTrace(out); + out.println("EXPECTED_SUBSEQUENT_MESSAGE"); + } + + assertThat(actual.toString()).contains("EXPECTED_SUBSEQUENT_MESSAGE"); + } + + private StatusRuntimeException newStatusRuntimeException(Code code, String message, List details) { + List anyDetails = details.stream().map(Any::pack).collect(Collectors.toList()); + com.google.rpc.Status status = com.google.rpc.Status.newBuilder() + .setCode(code.getNumber()) + .setMessage(message) + .addAllDetails(anyDetails) + .build(); + return StatusProto.toStatusRuntimeException(status); + } +} diff --git a/java/src/test/java/org/hyperledger/fabric/client/GatewayExceptionTest.java b/java/src/test/java/org/hyperledger/fabric/client/GatewayExceptionTest.java index 5624ebc75..26d204252 100644 --- a/java/src/test/java/org/hyperledger/fabric/client/GatewayExceptionTest.java +++ b/java/src/test/java/org/hyperledger/fabric/client/GatewayExceptionTest.java @@ -6,83 +6,10 @@ package org.hyperledger.fabric.client; -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.protobuf.Any; -import com.google.rpc.Code; import io.grpc.StatusRuntimeException; -import io.grpc.protobuf.StatusProto; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.hyperledger.fabric.protos.gateway.ErrorDetail; -import org.junit.jupiter.api.Test; - -public final class GatewayExceptionTest { - @Test - void error_details_are_printed() { - List details = Arrays.asList( - ErrorDetail.newBuilder() - .setAddress("ADDRESS1") - .setMspId("MSPID1") - .setMessage("MESSAGE1") - .build(), - ErrorDetail.newBuilder() - .setAddress("ADDRESS2") - .setMspId("MSPID2") - .setMessage("MESSAGE2") - .build()); - GatewayException e = new GatewayException(newStatusRuntimeException(Code.ABORTED, "STATUS_MESSAGE", details)); - - ByteArrayOutputStream actual = new ByteArrayOutputStream(); - try (PrintStream out = new PrintStream(actual)) { - e.printStackTrace(out); - } - - List expected = details.stream() - .flatMap(detail -> Stream.of(detail.getAddress(), detail.getMspId(), detail.getMessage())) - .collect(Collectors.toList()); - assertThat(actual.toString()).contains(expected); - } - - @Test - void message_from_StatusRuntimeException_is_printed() { - GatewayException e = new GatewayException( - newStatusRuntimeException(Code.ABORTED, "STATUS_MESSAGE", Collections.emptyList())); - - ByteArrayOutputStream actual = new ByteArrayOutputStream(); - try (PrintStream out = new PrintStream(actual)) { - e.printStackTrace(out); - } - - String expected = e.getCause().getLocalizedMessage(); - assertThat(actual.toString()).contains(expected); - } - - @Test - void print_stream_passed_to_printStackTrace_not_closed() { - GatewayException e = new GatewayException(newStatusRuntimeException(Code.ABORTED, "", Collections.emptyList())); - - ByteArrayOutputStream actual = new ByteArrayOutputStream(); - try (PrintStream out = new PrintStream(actual)) { - e.printStackTrace(out); - out.println("EXPECTED_SUBSEQUENT_MESSAGE"); - } - - assertThat(actual.toString()).contains("EXPECTED_SUBSEQUENT_MESSAGE"); - } - private StatusRuntimeException newStatusRuntimeException(Code code, String message, List details) { - List anyDetails = details.stream().map(Any::pack).collect(Collectors.toList()); - com.google.rpc.Status status = com.google.rpc.Status.newBuilder() - .setCode(code.getNumber()) - .setMessage(message) - .addAllDetails(anyDetails) - .build(); - return StatusProto.toStatusRuntimeException(status); +final class GatewayExceptionTest extends CommonGatewayExceptionTest { + protected Exception newInstance(final StatusRuntimeException e) { + return new GatewayException(e); } } diff --git a/java/src/test/java/org/hyperledger/fabric/client/GatewayRuntimeExceptionTest.java b/java/src/test/java/org/hyperledger/fabric/client/GatewayRuntimeExceptionTest.java new file mode 100644 index 000000000..532662f30 --- /dev/null +++ b/java/src/test/java/org/hyperledger/fabric/client/GatewayRuntimeExceptionTest.java @@ -0,0 +1,15 @@ +/* + * Copyright 2024 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.hyperledger.fabric.client; + +import io.grpc.StatusRuntimeException; + +final class GatewayRuntimeExceptionTest extends CommonGatewayExceptionTest { + protected Exception newInstance(final StatusRuntimeException e) { + return new GatewayRuntimeException(e); + } +}