blob: 556a159c1f9d9ac836430caf8cd4db89a74d37a5 [file] [log] [blame]
/*
* Copyright (c) 2010-2020 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
package org.eclipse.scout.sdk.core.s.environment;
import static java.lang.System.lineSeparator;
import static java.util.Collections.unmodifiableCollection;
import static org.eclipse.scout.sdk.core.log.SdkLog.onTrace;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import org.eclipse.scout.sdk.core.log.SdkLog;
import org.eclipse.scout.sdk.core.util.FinalValue;
import org.eclipse.scout.sdk.core.util.SdkException;
import org.eclipse.scout.sdk.core.util.Strings;
/**
* <h3>{@link SdkFuture}</h3>
*
* @since 7.1.0
*/
public class SdkFuture<V> extends CompletableFuture<Supplier<V>> implements IFuture<V> {
/**
* Creates a completed {@link IFuture} with the specified result.
*
* @param result
* The result of the {@link IFuture}. May be {@code null}.
* @return An already completed {@link IFuture} with the specified value.
*/
public static <T> IFuture<T> completed(T result) {
return completed(result, null);
}
/**
* Creates a completed {@link IFuture} with the specified result or exception.
*
* @param result
* The result of the {@link IFuture}. May be {@code null}.
* @param error
* The exception of the {@link IFuture}. May be {@code null}.
* @return An already completed {@link IFuture} with the specified results.
*/
public static <T> IFuture<T> completed(T result, Throwable error) {
return completed(() -> result, error);
}
/**
* Creates a completed {@link IFuture} that uses the result supplier specified to compute the value.
*
* @param resultExtractor
* The {@link Supplier} that provides the result of the resulting {@link IFuture}. The {@link Supplier} is
* only invoked if the provided error is {@code null}.
* @param error
* The exception of the {@link IFuture}. May be {@code null}.
* @return An already completed {@link IFuture} either holding the specified error (if not {@code null}) or the result
* as computed by the {@link Supplier} specified.
*/
public static <T> IFuture<T> completed(Supplier<T> resultExtractor, Throwable error) {
return new SdkFuture<T>().doCompletion(false, error, resultExtractor);
}
/**
* Waits until all of the futures specified have completed. A future is completed if it ends successfully, threw an
* exception or was canceled.<br>
* If there is at least one {@link Future} that ended exceptionally, this method throws an exception as well.
*
* @param futures
* The futures to wait for
* @throws CompositeException
* if at least one future completed exceptionally. This exception holds all {@link Throwable}s of the failed
* {@link Future}s.
*/
@SuppressWarnings("squid:S1166") // "Exception handlers should preserve the original exceptions". this is given as the exceptions are collected and then thrown at once
public static void awaitAll(Iterable<? extends Future<?>> futures) {
if (futures == null) {
return;
}
Collection<Throwable> errors = new ArrayList<>();
for (Future<?> future : futures) {
if (future == null) {
continue;
}
try {
future.get();
}
catch (ExecutionException e) {
errors.add(e.getCause());
}
catch (InterruptedException e) {
throw new SdkException(e);
}
catch (CancellationException e) {
SdkLog.debug("Cancellation silently ignored", onTrace(e));
}
}
if (errors.isEmpty()) {
return;
}
throw new CompositeException(errors);
}
/**
* Waits until all of the futures specified have completed. A future is completed if it ends successfully, threw an
* exception or was canceled.<br>
* Futures that end exceptionally are logged with level error. The log is only written when all the futures have
* completed.
*
* @param futures
* The futures to wait for
*/
public static void awaitAllLoggingOnError(Iterable<IFuture<?>> futures) {
try {
awaitAll(futures);
}
catch (CompositeException e) {
e.exceptions().forEach(SdkLog::error);
}
}
public static class CompositeException extends RuntimeException {
private static final long serialVersionUID = -2565677977298841153L;
private final Collection<Throwable> m_throwables;
public CompositeException(Collection<Throwable> throwables) {
super("Composite exception was thrown with embedded exceptions (see details before)");
m_throwables = new ArrayList<>(throwables);
}
public Collection<Throwable> exceptions() {
return unmodifiableCollection(m_throwables);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("Nested Exception: [").append(lineSeparator());
for (Throwable throwable : m_throwables) {
builder.append(throwable.toString()).append(Strings.fromThrowable(throwable)).append(lineSeparator());
}
builder.append(']').append(lineSeparator()).append(super.toString());
return builder.toString();
}
}
@Override
public SdkFuture<V> awaitDoneThrowingOnErrorOrCancel() {
result();
return this;
}
@Override
public SdkFuture<V> awaitDoneThrowingOnError() {
try {
join();
}
catch (CancellationException e) {
SdkLog.debug("Cancellation silently ignored", onTrace(e));
}
return this;
}
protected SdkFuture<V> doCompletion(boolean isCanceled, Throwable error, Supplier<V> resultExtractor) {
if (isCanceled) {
completeExceptionally(new CancellationException());
}
else {
if (error == null) {
if (resultExtractor == null) {
complete(() -> null); // the supplier should never be null. only the result provided by the supplier may be null
}
else {
FinalValue<V> cachedResult = new FinalValue<>(); // use a final value so that the extractor is only executed once
complete(() -> cachedResult.computeIfAbsentAndGet(resultExtractor));
}
}
else {
SdkLog.debug("Asynchronous task completed with exception", error);
completeExceptionally(error);
}
}
return this;
}
@Override
public V result() {
try {
return join().get();
}
catch (CompletionException e) {
SdkLog.debug("Future completed exceptionally.", e);
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
throw e;
}
}
}