ErrorObserverMiddleware.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;

/**
 * Provides a simple middleware that allows applications to observe any {@link java.lang.Error}s thrown by the
 * dispatch of an incoming request.
 */
public final class ErrorObserverMiddleware implements Object {
    private final Object _next;
    private final Consumer<Error> _errorObserver;

    /**
     * Constructs a ErrorObserverMiddleware.
     *
     * @param next the next dispatcher in the chain
     * @param errorObserver the error observer. If the provided observer throws an exception while
     *     observing an error, this exception replaces the observed error for the remainder of the dispatch.
     */
    public ErrorObserverMiddleware(Object next, Consumer<Error> errorObserver) {
        _next = next;
        _errorObserver = errorObserver;
    }

    @Override
    public CompletionStage<OutgoingResponse> dispatch(IncomingRequest request)
        throws UserException {
        try {
            return _next.dispatch(request)
                .exceptionally(
                    exception -> {
                        // _errorObserver can throw an exception that effectively replaces
                        // exception for dependent completion stages.
                        if (exception instanceof Error error) {
                            _errorObserver.accept(error);
                            throw error;
                        } else if (exception instanceof CompletionException completionException
                            && completionException.getCause() instanceof Error error) {
                            // This can occur when the closure of a parent completion stage throws an error.
                            _errorObserver.accept(error);
                        }

                        if (exception instanceof RuntimeException runtimeException) {
                            // Rethrow as-is. Note that Java does not wrap CompletionException in CompletionException.
                            throw runtimeException;
                        }

                        // exception is a Throwable that is not an Error or a RuntimeException.
                        // We can't throw it so we marshal this exception into a response.
                        return request.current.createOutgoingResponse(exception);
                    });
        } catch (Error error) {
            // synchronous error
            _errorObserver.accept(error);
            throw error;
        }
    }
}