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

sirius.kernel.async.Promise Maven / Gradle / Ivy

Go to download

Provides common core classes and the microkernel powering all Sirius applications

There is a newer version: 12.9.1
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.async;

import com.google.common.collect.Lists;
import sirius.kernel.commons.Callback;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.ValueHolder;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.HandledException;
import sirius.kernel.health.Log;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Represents a value which is computed by another task or thread.
 * 

* This is the core mechanism of non-blocking communication between different threads or systems. A value which * is not immediately available is returned as Promise. This promise is either successfully fulfilled * or supplied with a failure. In any case a {@link CompletionHandler} can be attached which is notified once * the computation is completed. *

* Since promises can be chained ({@link #chain(Promise)}, {@link #failChain(Promise, sirius.kernel.commons.Callback)}) * or aggregated ({@link Tasks#sequence(java.util.List)}, {@link Barrier}) complex computations can be glued * together using simple components. * * @param contains the type of the value which is to be computed */ public class Promise { private ValueHolder value; private Throwable failure; private volatile boolean logErrors = true; private List> handlers = Lists.newArrayListWithCapacity(2); /** * Creates a new promise which can be fulfilled later. */ public Promise() { } /** * Creates an instantly successful promise containing the given value. * * @param successValue the value to fulfill the promise with */ public Promise(V successValue) { success(successValue); } /** * Returns the value of the promise or null if not completed yet. * * @return the value of the promised computation. This method will not block, so null is returned if * the computation has not finished (or failed) yet. */ public V get() { return value != null ? value.get() : null; } /** * Marks the promise as successful and completed with the given value. * * @param value the value to be used as promised result. * @return this for fluent method chaining */ public Promise success(@Nullable final V value) { this.value = new ValueHolder<>(value); for (final CompletionHandler handler : handlers) { completeHandler(value, handler); } return this; } /* * Invokes the onSuccess method of given CompletionHandler. */ private void completeHandler(final V value, final CompletionHandler handler) { try { handler.onSuccess(value); } catch (Exception e) { Exceptions.handle(Tasks.LOG, e); } } /** * Marks the promise as failed due to the given error. * * @param exception the error to be used as reason for failure. * @return this for fluent method chaining */ public Promise fail(@Nonnull final Throwable exception) { this.failure = exception; if (logErrors) { Exceptions.handle(Tasks.LOG, exception); } else if (Tasks.LOG.isFINE() && !(exception instanceof HandledException)) { Tasks.LOG.FINE(Exceptions.createHandled().error(exception)); } for (final CompletionHandler handler : handlers) { failHandler(exception, handler); } return this; } /* * Invokes the onFailure method of given CompletionHandler. */ private void failHandler(final Throwable exception, final CompletionHandler handler) { try { handler.onFailure(exception); } catch (Exception e) { Exceptions.handle(Tasks.LOG, e); } } /** * Determines if the promise is completed yet. * * @return true if the promise has either successfully completed or failed yet, false otherwise. */ public boolean isCompleted() { return isFailed() || isSuccessful(); } /** * Determines if the promise is failed. * * @return true if the promise failed, false otherwise. */ public boolean isFailed() { return failure != null; } /** * Determines if the promise was successfully completed yet. * * @return true if the promise was successfully completed, false otherwise. */ public boolean isSuccessful() { return value != null && !isFailed(); } /** * Waits until the promise is completed. * * @param timeout the maximal time to wait for the completion of this promise. * @return true if the promise was completed within the given timeout, false otherwise */ public boolean await(Duration timeout) { if (!isCompleted()) { awaitBlocking(timeout); } return isCompleted(); } /* * Waits for a yet uncompleted promise by blocking the current thread via a Condition. */ @SuppressWarnings({"squid:S899", "squid:S2274"}) @Explain("We cannot use a loop here and we don't care about the return value.") private void awaitBlocking(Duration timeout) { Lock lock = new ReentrantLock(); Condition completed = lock.newCondition(); onComplete(new CompletionHandler() { @Override public void onSuccess(@Nullable Object value) throws Exception { lock.lock(); try { completed.signalAll(); } finally { lock.unlock(); } } @Override public void onFailure(Throwable throwable) throws Exception { lock.lock(); try { completed.signalAll(); } finally { lock.unlock(); } } }); if (!isCompleted()) { lock.lock(); try { completed.await(timeout.getSeconds(), TimeUnit.SECONDS); } catch (InterruptedException e) { Exceptions.ignore(e); Thread.currentThread().interrupt(); } finally { lock.unlock(); } } } /** * Returns the failure which was the reason for this promise to have failed. * * @return the error which made this promise fail, or null if the promnise is still running or not * completed yet. */ public Throwable getFailure() { return failure; } /** * Used the result of this promise to create a new one by passing the resulting value into the given mapper. * * @param mapper the mapper to transform the promised value of this promise. * @param the resulting type of the mapper * @return a new promise which will be either contain the mapped value or which fails if either this promise fails * or if the mapper throws an exception. */ @Nonnull public Promise map(@Nonnull final Function mapper) { final Promise result = new Promise<>(); mapChain(result, mapper); return result; } /** * Uses to result of this promise to generate a new promise using the given mapper. * * @param mapper the mapper to transform the promised value of this promise. * @param the resulting type of the mapper * @return a new promise which will be either contain the mapped value or which fails if either this promise fails * or if the mapper throws an exception. */ @Nonnull public Promise flatMap(@Nonnull final Function> mapper) { final Promise result = new Promise<>(); onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { try { mapper.apply(value).chain(result); } catch (Exception throwable) { result.fail(throwable); } } @Override public void onFailure(Throwable throwable) { result.fail(throwable); } }); return result; } /** * Chains this promise to the given one. *

* Connects both, the successful path as well as the failure handling of this promise to the given one. * * @param promise the promise to be used as completion handler for this. */ public void chain(@Nonnull final Promise promise) { onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { promise.success(value); } @Override public void onFailure(Throwable throwable) { promise.fail(throwable); } }); } /** * Chains this promise to the given future. *

* Connects both, the successful path as well as the failure handling of this promise to the given future. * * @param future the future to be used as completion handler for this. */ public void chain(@Nonnull Future future) { onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { future.success(); } @Override public void onFailure(Throwable throwable) { future.fail(throwable); } }); } /** * Returns this promise as a future. * * @return this promise as future */ public Future asFuture() { return (Future) this; } /** * Chains this promise to the given one, by transforming the result value of this promise using the given mapper. * * @param promise the promise to be used as completion handler for this. * @param mapper the mapper to be used to convert the result of this promise to the value used to the given * promise. * @param type of the value expected by the given promise. */ public void mapChain(@Nonnull final Promise promise, @Nonnull final Function mapper) { onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { try { promise.success(mapper.apply(value)); } catch (Exception e) { promise.fail(e); } } @Override public void onFailure(Throwable throwable) { promise.fail(throwable); } }); } /** * Forwards failures to the given promise, while sending successful value to the given successHandler. * * @param promise the promise to be supplied with any failure of this promise. * @param successHandler the handler used to process successfully computed values. * @param type of promised value of the given promise. * @return this for fluent method chaining */ @Nonnull public Promise failChain(@Nonnull final Promise promise, @Nonnull final Callback successHandler) { return onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { try { successHandler.invoke(value); } catch (Exception e) { promise.fail(e); } } @Override public void onFailure(Throwable throwable) { promise.fail(throwable); } }); } /** * Adds a completion handler to this promise. *

* If the promise is already completed, the handler is immediately invoked. * * @param handler the handler to be notified once the promise is completed. A promise can notify more than one * handler. * @return this for fluent method chaining */ @Nonnull @SuppressWarnings("squid:S2589") @Explain("We really want to ensure that the given value is not null and not just rely on annotation.") public Promise onComplete(@Nonnull CompletionHandler handler) { if (handler != null) { if (isSuccessful()) { completeHandler(get(), handler); } else if (isFailed()) { failHandler(getFailure(), handler); } else { this.handlers.add(handler); logErrors = false; } } return this; } /** * Adds a completion handler to this promise which only handles the successful completion of the promise. *

* If the promise is already completed, the handler is immediately invoked. * * @param successHandler the handler to be notified once the promise is completed. A promise can notify more than * one handler. * @return this for fluent method chaining */ @Nonnull public Promise onSuccessCallback(@Nonnull final Callback successHandler) { return onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { try { successHandler.invoke(value); } catch (Exception t) { fail(t); } } @Override public void onFailure(Throwable throwable) { // Not used for success callbacks } }); } /** * Adds a completion handler to this promise which only handles the successful completion of the promise. *

* If the promise is already completed, the handler is immediately invoked. * * @param successHandler the handler to be notified once the promise is completed. A promise can notify more than * one handler. * @return this for fluent method chaining */ @Nonnull public Promise onSuccess(@Nonnull final Consumer successHandler) { return onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { try { successHandler.accept(value); } catch (Exception t) { fail(t); } } @Override public void onFailure(Throwable throwable) { // Not used for success callbacks } }); } /** * Adds a completion handler to this promise which only handles the failed completion of the promise. *

* If the promise is already completed, the handler is immediately invoked. * * @param failureHandler the handler to be notified once the promise is completed. A promise can notify more than * one handler. * @return this for fluent method chaining */ @Nonnull public Promise onFailureCallback(@Nonnull final Callback failureHandler) { logErrors = false; return onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { // Not used for failure callbacks } @Override public void onFailure(Throwable throwable) throws Exception { failureHandler.invoke(throwable); } }); } /** * Adds a completion handler to this promise which only handles the failed completion of the promise. *

* If the promise is already completed, the handler is immediately invoked. * * @param failureHandler the handler to be notified once the promise is completed. A promise can notify more than * one handler. * @return this for fluent method chaining */ @Nonnull public Promise onFailure(@Nonnull final Consumer failureHandler) { logErrors = false; return onComplete(new CompletionHandler() { @Override public void onSuccess(V value) throws Exception { // will not be invoked } @Override public void onFailure(Throwable throwable) throws Exception { failureHandler.accept(throwable); } }); } /** * Provides a handler which is invoked when the promise completes. * * @param completionHandler the handler which is invoked on completion (successful or not). * @return this for fluent method chaining */ public Promise then(@Nonnull Runnable completionHandler) { return onComplete(new CompletionHandler() { @Override public void onSuccess(@Nullable V value) throws Exception { completionHandler.run(); } @Override public void onFailure(@Nonnull Throwable throwable) throws Exception { completionHandler.run(); } }); } /** * Provides a handler which is invoked when the promise completes. *

* The given handler is either supplied with the success value of the promise, wrapped as optional * or with an empty optional, if the promise failed. *

* Note that using this approach, one cannot determine if the promise failed or was completed * with null. * * @param completionHandler the handler which is invoked on completion (successful or not). * @return this for fluent method chaining */ public Promise then(@Nonnull Consumer> completionHandler) { return onComplete(new CompletionHandler() { @Override public void onSuccess(@Nullable V value) throws Exception { completionHandler.accept(Optional.ofNullable(value)); } @Override public void onFailure(@Nonnull Throwable throwable) throws Exception { completionHandler.accept(Optional.empty()); } }); } /** * Disables the error logging even if no failure handlers are present. * * @return this for fluent method chaining */ public Promise doNotLogErrors() { logErrors = false; return this; } /** * Adds an error handler, which handles failures by logging them to the given {@link Log} *

* By default, if no explicit completion handler is present, all failures are logged using the async * logger. * * @param log the logger to be used when logging an error. * @return this for fluent method chaining */ @Nonnull public Promise handleErrors(@Nonnull final Log log) { return onFailure(ex -> Exceptions.handle(log, ex)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy