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 java.lang.Error 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 error observer throws an exception while
     *     observing an error, this exception replaces the 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;
        }
    }
}