com.github.rahulsom.grooves.queries.internal.QueryExecutor Maven / Gradle / Ivy
package com.github.rahulsom.grooves.queries.internal;
import com.github.rahulsom.grooves.api.AggregateType;
import com.github.rahulsom.grooves.api.EventApplyOutcome;
import com.github.rahulsom.grooves.api.GroovesException;
import com.github.rahulsom.grooves.api.events.BaseEvent;
import com.github.rahulsom.grooves.api.events.DeprecatedBy;
import com.github.rahulsom.grooves.api.events.Deprecates;
import com.github.rahulsom.grooves.api.events.RevertEvent;
import com.github.rahulsom.grooves.api.snapshots.internal.BaseSnapshot;
import io.reactivex.Flowable;
import org.jetbrains.annotations.NotNull;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.github.rahulsom.grooves.api.EventApplyOutcome.RETURN;
import static com.github.rahulsom.grooves.queries.internal.Utils.ids;
import static com.github.rahulsom.grooves.queries.internal.Utils.stringify;
import static io.reactivex.Flowable.*;
/**
* Executes a query. This makes a query more flexible by allowing the use of different query
* executors.
*
* @param The type of {@link AggregateT}'s id
* @param The aggregate over which the query executes
* @param The type of the {@link EventT}'s id field
* @param The type of the Event
* @param The type of the {@link SnapshotT}'s id field
* @param The type of the Snapshot
* @param A reference to the query type.
*
* @author Rahul Somasunderam
*/
public class QueryExecutor<
AggregateIdT,
AggregateT extends AggregateType,
EventIdT,
EventT extends BaseEvent,
SnapshotIdT,
SnapshotT extends BaseSnapshot,
QueryT extends BaseQuery
> implements Executor {
protected final Logger log = LoggerFactory.getLogger(getClass());
private static List plus(List list, T element) {
List retval = new ArrayList<>();
retval.addAll(list);
retval.add(element);
return retval;
}
/**
* Applies all revert events from a list and returns the list with only valid forward events.
*
* @param events The list of events
*
* @return An Flowable of forward only events
*/
@NotNull
@Override
public Flowable applyReverts(@NotNull Flowable events) {
return events.toList().toFlowable().flatMap(eventList -> {
log.debug(" Event Ids (includes reverts that won't be applied): {}",
ids(eventList));
List forwardEvents = new ArrayList<>();
while (!eventList.isEmpty()) {
EventT head = eventList.remove(eventList.size() - 1);
if (head instanceof RevertEvent) {
final EventIdT revertedEventId =
(EventIdT) ((RevertEvent) head).getRevertedEventId();
final Optional revertedEvent = eventList.stream()
.filter(it -> Objects.equals(it.getId(), revertedEventId))
.findFirst();
if (revertedEvent.isPresent()) {
eventList.remove(revertedEvent.get());
} else {
throw new GroovesException(String.format(
"Cannot revert event that does not exist in unapplied list - %s",
String.valueOf(revertedEventId)));
}
} else {
forwardEvents.add(0, head);
}
}
assert forwardEvents.stream().noneMatch(it -> it instanceof RevertEvent);
return fromIterable(forwardEvents);
});
}
@NotNull
@Override
public Flowable applyEvents(
@NotNull BaseQuery query,
@NotNull SnapshotT initialSnapshot,
@NotNull Flowable events,
@NotNull List> deprecatesList,
@NotNull AggregateT aggregate) {
final AtomicBoolean stopApplyingEvents = new AtomicBoolean(false);
// s -> snapshotObservable
return events.reduce(just(initialSnapshot), (s, event) -> s.flatMap(snapshot -> {
if (!query.shouldEventsBeApplied(snapshot) || stopApplyingEvents.get()) {
return just(snapshot);
} else {
log.debug(" -> Applying Event: {}", event);
if (event instanceof Deprecates) {
Deprecates deprecatesEvent =
(Deprecates) event;
return applyDeprecates(
deprecatesEvent, query, events, deprecatesList, aggregate);
} else if (event instanceof DeprecatedBy) {
DeprecatedBy deprecatedByEvent =
(DeprecatedBy) event;
return applyDeprecatedBy(deprecatedByEvent, snapshot);
} else {
String methodName = "apply" + event.getClass().getSimpleName();
return callMethod((QueryT) query, methodName, snapshot, event)
.flatMap(retval -> handleMethodResponse(
stopApplyingEvents, snapshot, methodName, retval));
}
}
})).toFlowable().flatMap(it -> it);
}
/**
* Decides how to proceed after inspecting the response of a method that returns an
* {@link EventApplyOutcome}.
*
* @param stopApplyingEvents Whether a previous decision has been made to stop applying new
* events
* @param snapshot The snapshot on which events are being added
* @param methodName The name of the method that was called
* @param retval The outcome of calling the method
*
* @return The snapshot after deciding what to do with the {@link EventApplyOutcome}
*/
private Flowable extends SnapshotT> handleMethodResponse(
AtomicBoolean stopApplyingEvents, SnapshotT snapshot, String methodName,
EventApplyOutcome retval) {
switch (retval) {
case RETURN:
stopApplyingEvents.set(true);
return just(snapshot);
case CONTINUE:
return just(snapshot);
default:
throw new GroovesException(
String.format("Unexpected value from calling '%s'", methodName));
}
}
/**
* Applies a {@link DeprecatedBy} event to a snapshot.
*
* @param event The {@link DeprecatedBy} event
* @param snapshot The snapshot computed until before this event
*
* @return The snapshot after applying the {@link DeprecatedBy} event
*/
@SuppressWarnings("GrMethodMayBeStatic")
Flowable applyDeprecatedBy(
final DeprecatedBy event,
SnapshotT snapshot) {
return fromPublisher(event.getDeprecatorObservable())
.reduce(snapshot, (snapshotT, aggregate) -> {
log.info(" -> {} will cause redirect to {}", event, aggregate);
snapshotT.setDeprecatedBy(aggregate);
return snapshotT;
}).toFlowable();
}
/**
* Applies a {@link Deprecates} event to a snapshot.
*
* @param event The {@link Deprecates} event
* @param query The Query Util instance
* @param events All {@link EventT}s that have been gathered so far
* @param deprecatesEvents The list of {@link Deprecates} events that have been collected so
* far
* @param aggregate The current aggregate
*
* @return The snapshot after applying the {@link Deprecates} event
*/
Flowable applyDeprecates(
final Deprecates event,
final BaseQuery query,
final Flowable events,
final List> deprecatesEvents,
AggregateT aggregate) {
log.info(" -> {} will cause recomputation", event);
final SnapshotT newSnapshot = query.createEmptySnapshot();
newSnapshot.setAggregate(aggregate);
return fromPublisher(event.getConverseObservable()).flatMap(converse ->
fromPublisher(event.getDeprecatedObservable())
.flatMap(deprecatedAggregate -> {
log.debug(" -> Deprecated Aggregate is: {}. Converse is: {}",
deprecatedAggregate, converse);
query.addToDeprecates(newSnapshot, deprecatedAggregate);
Flowable concatenatedEvents =
events.concatWith(
fromPublisher(query.findEventsBefore((EventT) converse))
).cache();
return concatenatedEvents
.filter(it -> !isDeprecatesOrConverse(event, converse, it))
.toSortedList(Comparator.comparing(EventT::getTimestamp))
.toFlowable()
.doOnNext(it ->
log.debug("Reassembled Events: {}", stringify(it)))
.flatMap(sortedEvents -> applyEvents(
query, newSnapshot,
applyReverts(fromIterable(sortedEvents)),
plus(deprecatesEvents, event), aggregate));
}));
}
private boolean isDeprecatesOrConverse(
Deprecates event,
DeprecatedBy converse,
EventT it) {
EventIdT id = it.getId();
return Objects.equals(id, event.getId()) || Objects.equals(id, converse.getId());
}
/**
* Calls a method on a Query Util instance.
*
* @param query The Query Util instance
* @param methodName The method to be called
* @param snapshot The snapshot to be passed to the method
* @param event The event to be passed to the method
*
* @return An observable returned by the method, or the result of calling onException on the
* Util instance, or an Observable that asks to RETURN if that fails.
*/
protected Flowable callMethod(
QueryT query, String methodName, final SnapshotT snapshot, final EventT event) {
try {
final Method method =
query.getClass().getMethod(methodName, event.getClass(), snapshot.getClass());
return fromPublisher(
(Publisher) method.invoke(query, event, snapshot));
} catch (Exception e1) {
return handleException(query, methodName, snapshot, event, e1);
}
}
private Flowable handleException(
QueryT query, String methodName, SnapshotT snapshot, EventT event, Exception e1) {
try {
return fromPublisher(query.onException(e1, snapshot, event));
} catch (Exception e2) {
String description = String.format(
"{Snapshot: %s; Event: %s; method: %s; originalException: %s}",
String.valueOf(snapshot), String.valueOf(event), methodName,
String.valueOf(e1));
log.error(String.format("Exception thrown while calling exception handler. %s",
description), e2);
return just(RETURN);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy