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

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 org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.github.rahulsom.grooves.queries.internal.Utils.stringifyEventIds;
import static com.github.rahulsom.grooves.queries.internal.Utils.stringifyEvents;
import static org.codehaus.groovy.runtime.InvokerHelper.invokeMethod;
import static rx.Observable.just;

/**
 * Executes a query. This makes a query more flexible by allowing the use of different query
 * executors.
 *
 * @param   The aggregate over which the query executes
 * @param     The type of the Event's id field
 * @param       The type of the Event
 * @param  The type of the Snapshot's id field
 * @param    The type of the Snapshot
 *
 * @author Rahul Somasunderam
 */
public class QueryExecutor<
        AggregateT extends AggregateType,
        EventIdT,
        EventT extends BaseEvent,
        SnapshotIdT,
        SnapshotT extends BaseSnapshot
        >
        implements Executor {

    final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Applies all revert events from a list and returns the list with only valid forward events.
     *
     * @param events The list of events
     *
     * @return An Observable of forward only events
     */
    @Override
    public Observable applyReverts(Observable events) {

        return events.toList().flatMap(eventList -> {
            log.debug(String.format("     EventList: %s", stringifyEventIds(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 -> it.getId().equals(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 Observable.from(forwardEvents);
        });
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Observable applyEvents(
            final BaseQuery query,
            SnapshotT initialSnapshot,
            Observable events,
            final List> deprecatesList,
            final List aggregates, 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) {
                    return applyDeprecates(
                            (Deprecates) event,
                            query, aggregates, deprecatesList, aggregate);
                } else if (event instanceof DeprecatedBy) {
                    return applyDeprecatedBy(
                            (DeprecatedBy) event, snapshot);
                } else {
                    String methodName = "apply" + event.getClass().getSimpleName();
                    return callMethod(query, methodName, snapshot, event)
                            .flatMap(retval -> handleMethodResponse(
                                    stopApplyingEvents, snapshot, methodName, retval));
                }
            }
        })).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 Observable handleMethodResponse(
            AtomicBoolean stopApplyingEvents, SnapshotT snapshot, String methodName,
            EventApplyOutcome retval) {
        if (retval.equals(EventApplyOutcome.CONTINUE)) {
            return just(snapshot);
        } else if (retval.equals(EventApplyOutcome.RETURN)) {
            stopApplyingEvents.set(true);
            return just(snapshot);
        } else {
            throw new GroovesException(
                    "Unexpected value from calling '" + 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")
    Observable applyDeprecatedBy(
            final DeprecatedBy event, SnapshotT snapshot) {
        return event.getDeprecatorObservable().reduce(snapshot, (snapshotT, aggregateT) -> {
            log.info("        -> {} will cause redirect to {}", event, aggregateT);
            snapshotT.setDeprecatedBy(aggregateT);
            return snapshotT;
        });
    }

    /**
     * Applies a {@link Deprecates} event to a snapshot.
     *
     * @param event            The {@link Deprecates} event
     * @param util             The Query Util instance
     * @param allAggregates    All {@link AggregateType}s that have been deprecated by current
     *                         aggregate
     * @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
     */
    Observable applyDeprecates(
            final Deprecates event,
            final BaseQuery util,
            final List allAggregates,
            final List> deprecatesEvents,
            AggregateT aggregate) {

        log.info("        -> {} will cause recomputation", event);
        final SnapshotT newSnapshot = util.createEmptySnapshot();
        newSnapshot.setAggregate(aggregate);

        return event.getConverseObservable().flatMap(converse -> event.getDeprecatedObservable()
                .flatMap(deprecatedAggregate -> {
                    log.debug("        -> Deprecated Aggregate is: {}. Converse is: {}",
                            deprecatedAggregate, converse);
                    util.addToDeprecates(newSnapshot, deprecatedAggregate);

                    return util.findEventsForAggregates(
                            DefaultGroovyMethods.plus(allAggregates, deprecatedAggregate))
                            .filter(it -> !it.getId().equals(event.getId())
                                    && !it.getId().equals(converse.getId()))
                            .toSortedList((a, b) -> a.getTimestamp().compareTo(b.getTimestamp()))
                            .flatMap(sortedEvents -> {
                                log.debug("Reassembled Events: {}", stringifyEvents(sortedEvents));
                                Observable forwardEventsSortedBackwards =
                                        applyReverts(Observable.from(sortedEvents));
                                return applyEvents(
                                        util,
                                        newSnapshot,
                                        forwardEventsSortedBackwards,
                                        DefaultGroovyMethods.plus(deprecatesEvents, event),
                                        allAggregates,
                                        aggregate);
                            });
                }));

    }

    /**
     * Calls a method on a Query Util instance.
     *
     * @param util       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.
     */
    Observable callMethod(
            BaseQuery util,
            String methodName,
            final SnapshotT snapshot,
            final EventT event) {
        try {
            return (Observable) invokeMethod(
                    util, methodName, new Object[]{event, snapshot});
        } catch (Exception e1) {
            try {
                return util.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(EventApplyOutcome.RETURN);
            }

        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy