io.camunda.zeebe.scheduler.future.ActorFutureCollector Maven / Gradle / Ivy
The newest version!
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Camunda License 1.0. You may not use this file
* except in compliance with the Camunda License 1.0.
*/
package io.camunda.zeebe.scheduler.future;
import static java.util.Arrays.stream;
import static java.util.Collections.emptySet;
import io.camunda.zeebe.scheduler.ConcurrencyControl;
import io.camunda.zeebe.util.Either;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
/**
* Aggregates a number of {@code ActorFuture} objects into a single one. The aggregated future is
* completed when all individual futures have completed. If all futures complete with a value, the
* aggregated future returns the ordered list of said values. If one or more complete exceptionally,
* the aggregated future will complete exceptionally. The exception will have the exceptions of the
* individual futures aas suppressed exceptions. If exceptions occur, this does not interrupt the
* individual futures.
*
*
* var aggregated = of(future1, future2).stream().collect(new ActorFutureCollector<>(concurrencyControl));
*
*
* @param type of the value of each future
*/
public final class ActorFutureCollector
implements Collector, List>, ActorFuture>> {
private final ConcurrencyControl concurrencyControl;
public ActorFutureCollector(final ConcurrencyControl concurrencyControl) {
this.concurrencyControl = Objects.requireNonNull(concurrencyControl);
}
@Override
public Supplier>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer>, ActorFuture> accumulator() {
return List::add;
}
@Override
public BinaryOperator>> combiner() {
return (listA, listB) -> {
listA.addAll(listB);
return listA;
};
}
@Override
public Function>, ActorFuture>> finisher() {
return futures -> new CompletionWaiter<>(concurrencyControl, futures).get();
}
@Override
public Set characteristics() {
return emptySet();
}
private static final class CompletionWaiter implements Supplier>> {
private final ConcurrencyControl concurrencyControl;
private final List> pendingFutures;
private final Either[] results;
private ActorFuture> aggregated;
private CompletionWaiter(
final ConcurrencyControl concurrencyControl, final List> pendingFutures) {
this.concurrencyControl = concurrencyControl;
this.pendingFutures = new ArrayList<>(pendingFutures);
results = new Either[(pendingFutures.size())];
}
@Override
public ActorFuture> get() {
aggregated = concurrencyControl.createFuture();
if (pendingFutures.isEmpty()) {
aggregated.complete(Collections.emptyList());
} else {
for (int index = 0; index < pendingFutures.size(); index++) {
final var pendingFuture = pendingFutures.get(index);
final var currentIndex = index;
concurrencyControl.runOnCompletion(
pendingFuture,
(result, error) -> handleCompletion(pendingFuture, currentIndex, result, error));
}
}
return aggregated;
}
private void handleCompletion(
final ActorFuture pendingFuture,
final int currentIndex,
final V result,
final Throwable error) {
pendingFutures.remove(pendingFuture);
results[currentIndex] = error == null ? Either.right(result) : Either.left(error);
if (pendingFutures.isEmpty()) {
completeAggregatedFuture();
}
}
private void completeAggregatedFuture() {
final var aggregatedResult = stream(results).collect(Either.collector());
if (aggregatedResult.isRight()) {
aggregated.complete(aggregatedResult.get());
} else {
final var exception =
new Exception("Errors occurred, see suppressed exceptions for details");
aggregatedResult.getLeft().forEach(exception::addSuppressed);
aggregated.completeExceptionally(exception);
}
}
}
}