io.camunda.zeebe.gateway.grpc.ErrorMappingStreamObserver Maven / Gradle / Ivy
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Camunda License 1.0. You may not use this file
* except in compliance with the Camunda License 1.0.
*/
package io.camunda.zeebe.gateway.grpc;
import io.camunda.zeebe.gateway.Loggers;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.ServerCallStreamObserver;
import io.grpc.stub.StreamObserver;
/**
* A {@link ServerCallStreamObserver} decorator which will map errors to {@link StatusException}
* before passing them on to the {@code delegate}. It will additionally suppress cancel exceptions;
* by default, the gRPC library will throw an exception if one tries to complete a call which was
* already cancelled. As we don't handle this (yet), simply log the instance.
*
* @param the expected type of the response
*/
public final class ErrorMappingStreamObserver
extends ServerCallStreamObserver implements ServerStreamObserver {
private static final GrpcErrorMapper DEFAULT_ERROR_MAPPER = new GrpcErrorMapper();
private final ServerCallStreamObserver delegate;
private final GrpcErrorMapper errorMapper;
public ErrorMappingStreamObserver(final ServerCallStreamObserver delegate) {
this(delegate, DEFAULT_ERROR_MAPPER);
}
public ErrorMappingStreamObserver(
final ServerCallStreamObserver delegate, final GrpcErrorMapper errorMapper) {
this.delegate = delegate;
this.errorMapper = errorMapper;
suppressCancelException();
}
/**
* Returns a new {@link ErrorMappingStreamObserver} wrapping the given {@link StreamObserver}.
*
* @param streamObserver the observer to wrap
* @param the expected response type
* @throws IllegalArgumentException if the given {@code streamObserver} is not a {@link
* ServerCallStreamObserver}
* @return an {@link ErrorMappingStreamObserver} wrapping the given {@code streamObserver}
*/
public static ErrorMappingStreamObserver ofStreamObserver(
final StreamObserver streamObserver) {
if (!(streamObserver instanceof ServerCallStreamObserver)) {
throw new IllegalArgumentException(
String.format(
"Expected to wrap a server call stream observer, but got %s; this class can only be used server-side",
streamObserver.getClass()));
}
return new ErrorMappingStreamObserver<>(
(ServerCallStreamObserver) streamObserver);
}
@Override
public void disableAutoInboundFlowControl() {
delegate.disableAutoInboundFlowControl();
}
@Override
public void onNext(final GrpcResponseT value) {
delegate.onNext(value);
}
@Override
public void onError(final Throwable t) {
final Throwable mapped;
if (t instanceof StatusException || t instanceof StatusRuntimeException) {
mapped = t;
} else {
mapped = errorMapper.mapError(t);
}
delegate.onError(mapped);
}
@Override
public void onCompleted() {
delegate.onCompleted();
}
@Override
public boolean isCancelled() {
return delegate.isCancelled();
}
@Override
public void setOnCancelHandler(final Runnable onCancelHandler) {
delegate.setOnCancelHandler(onCancelHandler);
}
@Override
public void setCompression(final String compression) {
delegate.setCompression(compression);
}
@Override
public boolean isReady() {
return delegate.isReady();
}
@Override
public void setOnReadyHandler(final Runnable onReadyHandler) {
delegate.setOnReadyHandler(onReadyHandler);
}
@Override
public void request(final int count) {
delegate.request(count);
}
@Override
public void setMessageCompression(final boolean enable) {
delegate.setMessageCompression(enable);
}
@Override
public void setOnCloseHandler(final Runnable onCloseHandler) {
delegate.setOnCloseHandler(onCloseHandler);
}
private void suppressCancelException() {
delegate.setOnCancelHandler(this::onCancel);
}
private void onCancel() {
Loggers.GATEWAY_LOGGER.trace(
"Attempted to respond to a cancelled call, indicating the client most likely went away");
}
}