All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.grpc.core.ResponseHelper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.grpc.core;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;

import io.grpc.stub.StreamObserver;

/**
 * A number of helper methods to handle sending responses to a {@link StreamObserver}.
 */
public final class ResponseHelper {

    private ResponseHelper() {
    }

    /**
     * Complete a gRPC request.
     * 

* The request will be completed by calling {@link StreamObserver#onNext(Object)} using the * specified value then calling {@link StreamObserver#onCompleted()}. * * @param observer the {@link StreamObserver} to complete * @param value the value to use when calling {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void complete(StreamObserver observer, T value) { StreamObserver safe = SafeStreamObserver.ensureSafeObserver(observer); safe.onNext(value); safe.onCompleted(); } /** * Complete a gRPC request based on the result of a {@link CompletionStage}. *

* The request will be completed by calling {@link StreamObserver#onNext(Object)} using the * result obtained on completion of the specified {@link CompletionStage} and then calling * {@link StreamObserver#onCompleted()}. *

* If the {@link CompletionStage} completes with an error then {@link StreamObserver#onError(Throwable)} * will be called. * * @param observer the {@link StreamObserver} to complete * @param future the {@link CompletionStage} to use to obtain the value to use to call * {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void complete(StreamObserver observer, CompletionStage future) { future.whenComplete(completeWithResult(observer)); } /** * Asynchronously complete a gRPC request based on the result of a {@link CompletionStage}. *

* The request will be completed by calling {@link StreamObserver#onNext(Object)} using the * result obtained on completion of the specified {@link CompletionStage} and then calling * {@link StreamObserver#onCompleted()}. *

* If the {@link CompletionStage} completes with an error then {@link StreamObserver#onError(Throwable)} * will be called. *

* The execution will take place asynchronously on the fork-join thread pool. * * @param observer the {@link StreamObserver} to complete * @param future the {@link CompletionStage} to use to obtain the value to use to call * {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void completeAsync(StreamObserver observer, CompletionStage future) { future.whenCompleteAsync(completeWithResult(observer)); } /** * Asynchronously complete a gRPC request based on the result of a {@link CompletionStage}. *

* The request will be completed by calling {@link StreamObserver#onNext(Object)} using the * result obtained on completion of the specified {@link CompletionStage} and then calling * {@link StreamObserver#onCompleted()}. *

* If the {@link CompletionStage} completes with an error then {@link StreamObserver#onError(Throwable)} * will be called. * * @param observer the {@link StreamObserver} to complete * @param future the {@link CompletionStage} to use to obtain the value to use to call * {@link StreamObserver#onNext(Object)} * @param executor the {@link Executor} on which to execute the asynchronous * request completion * @param they type of the request result */ public static void completeAsync(StreamObserver observer, CompletionStage future, Executor executor) { future.whenCompleteAsync(completeWithResult(observer), executor); } /** * Complete a gRPC request based on the result of a {@link Callable}. *

* The request will be completed by calling {@link StreamObserver#onNext(Object)} using the * result obtained on completion of the specified {@link Callable} and then calling * {@link StreamObserver#onCompleted()}. *

* If the {@link Callable#call()} method throws an exception then {@link StreamObserver#onError(Throwable)} * will be called. * * @param observer the {@link StreamObserver} to complete * @param callable the {@link Callable} to use to obtain the value to use to call * {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void complete(StreamObserver observer, Callable callable) { try { observer.onNext(callable.call()); observer.onCompleted(); } catch (Throwable t) { observer.onError(t); } } /** * Asynchronously complete a gRPC request based on the result of a {@link Callable}. *

* The request will be completed by calling {@link StreamObserver#onNext(Object)} using the * result obtained on completion of the specified {@link Callable} and then calling * {@link StreamObserver#onCompleted()}. *

* If the {@link Callable#call()} method throws an exception then {@link StreamObserver#onError(Throwable)} * will be called. *

* The execution will take place asynchronously on the fork-join thread pool. * * @param observer the {@link StreamObserver} to complete * @param callable the {@link Callable} to use to obtain the value to use to call * {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void completeAsync(StreamObserver observer, Callable callable) { completeAsync(observer, CompletableFuture.supplyAsync(createSupplier(callable))); } /** * Asynchronously complete a gRPC request based on the result of a {@link Callable}. *

* The request will be completed by calling {@link StreamObserver#onNext(Object)} using the * result obtained on completion of the specified {@link Callable} and then calling * {@link StreamObserver#onCompleted()}. *

* If the {@link Callable#call()} method throws an exception then {@link StreamObserver#onError(Throwable)} * will be called. * * @param observer the {@link StreamObserver} to complete * @param callable the {@link Callable} to use to obtain the value to use to call * {@link StreamObserver#onNext(Object)} * @param executor the {@link Executor} on which to execute the asynchronous * request completion * @param they type of the request result */ public static void completeAsync(StreamObserver observer, Callable callable, Executor executor) { completeAsync(observer, CompletableFuture.supplyAsync(createSupplier(callable), executor)); } /** * Execute a {@link Runnable} task and on completion of the task complete the gRPC request by * calling {@link StreamObserver#onNext(Object)} using the specified result and then call * {@link StreamObserver#onCompleted()}. *

* If the {@link Runnable#run()} method throws an exception then {@link StreamObserver#onError(Throwable)} * will be called. * * @param observer the {@link StreamObserver} to complete * @param task the {@link Runnable} to execute * @param result the result to pass to {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void complete(StreamObserver observer, Runnable task, T result) { complete(observer, Executors.callable(task, result)); } /** * Asynchronously execute a {@link Runnable} task and on completion of the task complete the gRPC * request by calling {@link StreamObserver#onNext(Object)} using the specified result and then * call {@link StreamObserver#onCompleted()}. *

* If the {@link Runnable#run()} method throws an exception then {@link StreamObserver#onError(Throwable)} * will be called. *

* The task and and request completion will be executed on the fork-join thread pool. * * @param observer the {@link StreamObserver} to complete * @param task the {@link Runnable} to execute * @param result the result to pass to {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void completeAsync(StreamObserver observer, Runnable task, T result) { completeAsync(observer, Executors.callable(task, result)); } /** * Asynchronously execute a {@link Runnable} task and on completion of the task complete the gRPC * request by calling {@link StreamObserver#onNext(Object)} using the specified result and then * call {@link StreamObserver#onCompleted()}. *

* If the {@link Runnable#run()} method throws an exception then {@link StreamObserver#onError(Throwable)} * will be called. * * @param observer the {@link StreamObserver} to complete * @param task the {@link Runnable} to execute * @param result the result to pass to {@link StreamObserver#onNext(Object)} * @param executor the {@link Executor} on which to execute the asynchronous * request completion * @param they type of the request result */ public static void completeAsync(StreamObserver observer, Runnable task, T result, Executor executor) { completeAsync(observer, Executors.callable(task, result), executor); } /** * Send the values from a {@link Stream} to the {@link StreamObserver#onNext(Object)} method until the * {@link Stream} is exhausted call {@link StreamObserver#onCompleted()}. *

* If an error occurs whilst streaming results then {@link StreamObserver#onError(Throwable)} will be called. * * @param observer the {@link StreamObserver} to complete * @param stream the {@link Stream} of results to send to {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void stream(StreamObserver observer, Stream stream) { stream(observer, () -> stream); } /** * Asynchronously send the values from a {@link Stream} to the {@link StreamObserver#onNext(Object)} method until * the {@link Stream} is exhausted call {@link StreamObserver#onCompleted()}. *

* If an error occurs whilst streaming results then {@link StreamObserver#onError(Throwable)} will be called. * * @param observer the {@link StreamObserver} to complete * @param stream the {@link Stream} of results to send to {@link StreamObserver#onNext(Object)} * @param executor the {@link Executor} on which to execute the asynchronous * request completion * @param they type of the request result */ public static void streamAsync(StreamObserver observer, Stream stream, Executor executor) { executor.execute(() -> stream(observer, () -> stream)); } /** * Send the values from a {@link Stream} to the {@link StreamObserver#onNext(Object)} method until the * {@link Stream} is exhausted call {@link StreamObserver#onCompleted()}. *

* If an error occurs whilst streaming results then {@link StreamObserver#onError(Throwable)} will be called. * * @param observer the {@link StreamObserver} to complete * @param supplier the {@link Supplier} of the {@link Stream} of results to send to {@link StreamObserver#onNext(Object)} * @param they type of the request result */ public static void stream(StreamObserver observer, Supplier> supplier) { StreamObserver safe = SafeStreamObserver.ensureSafeObserver(observer); Throwable thrown = null; try { supplier.get().forEach(safe::onNext); } catch (Throwable t) { thrown = t; } if (thrown == null) { safe.onCompleted(); } else { safe.onError(thrown); } } /** * Asynchronously send the values from a {@link Stream} to the {@link StreamObserver#onNext(Object)} method * until the {@link Stream} is exhausted call {@link StreamObserver#onCompleted()}. *

* If an error occurs whilst streaming results then {@link StreamObserver#onError(Throwable)} will be called. * * @param observer the {@link StreamObserver} to complete * @param supplier the {@link Supplier} of the {@link Stream} of results to send to {@link StreamObserver#onNext(Object)} * @param executor the {@link Executor} on which to execute the asynchronous * request completion * @param they type of the request result */ public static void streamAsync(StreamObserver observer, Supplier> supplier, Executor executor) { executor.execute(() -> stream(observer, supplier)); } /** * Obtain a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method until * the {@link CompletionStage} completes then call {@link StreamObserver#onCompleted()}. *

* If the {@link CompletionStage} completes with an error then {@link StreamObserver#onError(Throwable)} * will be called instead of {@link StreamObserver#onCompleted()}. * * @param observer the {@link StreamObserver} to send values to and complete when the {@link CompletionStage} completes * @param stage the {@link CompletionStage} to await completion of * @param they type of the request result * * @return a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method */ // todo: a bit of a chicken or egg when used with Coherence streaming methods, isn't it? public static Consumer stream(StreamObserver observer, CompletionStage stage) { StreamObserver safe = SafeStreamObserver.ensureSafeObserver(observer); stage.whenComplete(completeWithoutResult(safe)); return safe::onNext; } /** * Obtain a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method until * the {@link CompletionStage} completes then asynchronously call {@link StreamObserver#onCompleted()} using the * fork-join thread pool. *

* If the {@link CompletionStage} completes with an error then {@link StreamObserver#onError(Throwable)} * will be called instead of {@link StreamObserver#onCompleted()}. * * @param observer the {@link StreamObserver} to send values to and complete when the {@link CompletionStage} completes * @param stage the {@link CompletionStage} to await completion of * @param they type of the request result * * @return a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method */ public static Consumer streamAsync(StreamObserver observer, CompletionStage stage) { StreamObserver safe = SafeStreamObserver.ensureSafeObserver(observer); stage.whenCompleteAsync(completeWithoutResult(safe)); return value -> CompletableFuture.runAsync(() -> safe.onNext(value)); } /** * Obtain a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method until * the {@link CompletionStage} completes then asynchronously call {@link StreamObserver#onCompleted()} using the executor * thread. *

* If the {@link CompletionStage} completes with an error then {@link StreamObserver#onError(Throwable)} * will be called instead of {@link StreamObserver#onCompleted()}. * * @param observer the {@link StreamObserver} to send values to and complete when the {@link CompletionStage} completes * @param stage the {@link CompletionStage} to await completion of * @param executor the {@link Executor} on which to execute the asynchronous * request completion * @param they type of the request result * * @return a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method */ public static Consumer streamAsync(StreamObserver observer, CompletionStage stage, Executor executor) { StreamObserver safe = SafeStreamObserver.ensureSafeObserver(observer); stage.whenCompleteAsync(completeWithoutResult(safe), executor); return value -> CompletableFuture.runAsync(() -> safe.onNext(value), executor); } /** * Obtain a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method. * @param observer the {@link StreamObserver} to complete * @param the type of the result * @param the type of the response * @return a {@link Consumer} that can be used to send values to the {@link StreamObserver#onNext(Object)} method */ public static BiConsumer completeWithResult(StreamObserver observer) { return new CompletionAction<>(observer, true); } /** * Obtain a {@link Consumer} that can be used to complete a {@link StreamObserver}. * @param observer the {@link StreamObserver} to complete * @param the type of the response * @return a {@link Consumer} that can be used to complete a {@link StreamObserver} */ public static BiConsumer completeWithoutResult(StreamObserver observer) { return new CompletionAction<>(observer, false); } /** * Convert a {@link Callable} to a {@link Supplier}. * @param callable the {@link Callable} to convert * @param the result returned by the {@link Callable} * @return a {@link Supplier} that wraps the {@link Callable} */ public static Supplier createSupplier(Callable callable) { return new CallableSupplier<>(callable); } /** * A {@link BiConsumer} that is used to handle completion of a * {@link CompletionStage} by forwarding * the result to a {@link StreamObserver}. * * @param the type of the {@link CompletionStage}'s result * @param the type of result expected by the {@link StreamObserver} */ private static class CompletionAction implements BiConsumer { private final StreamObserver observer; private final boolean sendResult; CompletionAction(StreamObserver observer, boolean sendResult) { this.observer = observer; this.sendResult = sendResult; } @Override @SuppressWarnings("unchecked") public void accept(T result, Throwable error) { if (error != null) { observer.onError(error); } else { if (sendResult) { observer.onNext((U) result); } observer.onCompleted(); } } } /** * A class that converts a {@link Callable} to a {@link Supplier}. * @param the type of result returned from the callable */ private static class CallableSupplier implements Supplier { private final Callable callable; CallableSupplier(Callable callable) { this.callable = callable; } @Override public T get() { try { return callable.call(); } catch (Exception e) { throw new CompletionException(e.getMessage(), e); } } } }