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

com.github.rahulsom.grooves.queries.internal.Utils Maven / Gradle / Ivy

The newest version!
package com.github.rahulsom.grooves.queries.internal;

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.TemporalSnapshot;
import com.github.rahulsom.grooves.api.snapshots.VersionedSnapshot;
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.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import static io.reactivex.Flowable.*;
import static java.util.stream.Collectors.toList;

/**
 * Utility objects and methods to help with Queries.
 *
 * @author Rahul Somasunderam
 */
public class Utils {

    private static final Collector JOIN_EVENT_IDS =
            Collectors.joining(", ");

    private static final Collector JOIN_EVENTS =
            Collectors.joining(",\n    ", "[\n    ", "\n]");

    private Utils() {
    }

    /**
     * Returns a snapshot or redirects to its deprecator.
     *
     * @param redirect           Whether a redirect is desirable
     * @param events             The sequence of events
     * @param it                 The snapshot
     * @param redirectedSnapshot A computation for the redirected snapshot
     * @param        The type of Aggregate
     * @param          The type of EventT's id
     * @param            The type of the event
     * @param       The type of SnapshotT's id
     * @param         The type of the snapshot
     *
     * @return An observable of a snapshot.
     */
    @NotNull
    public static <
            AggregateT,
            EventIdT,
            EventT extends BaseEvent,
            SnapshotIdT,
            SnapshotT extends BaseSnapshot
            > Flowable returnOrRedirect(
            boolean redirect,
            @NotNull List events,
            @NotNull SnapshotT it,
            @NotNull Supplier> redirectedSnapshot) {
        final EventT lastEvent =
                events.isEmpty() ? null : events.get(events.size() - 1);

        final boolean redirectToDeprecator =
                lastEvent != null
                        && lastEvent instanceof DeprecatedBy
                        && redirect;

        return fromPublisher(it.getDeprecatedByObservable())
                .flatMap(deprecatedBy -> redirectToDeprecator ? redirectedSnapshot.get() : just(it))
                .defaultIfEmpty(it);

    }

    /**
     * Computes forward only events. This could mean cancelling out events with their reverts
     * within a list, or sometimes, invoking the supplier of fallback events to get events.
     *
     * @param events                    The sequence of events
     * @param executor                  The executor to use for processing events
     * @param fallbackSnapshotAndEvents The fallback supplier
     * @param               The type of Aggregate
     * @param                 The type of EventT's id
     * @param                   The type of Event
     * @param              The type of SnapshotT's id
     * @param                The type of Snapshot
     * @param                   The type of Query
     * @param                The type of the query executor
     *
     * @return an observable of forward only events
     */
    @NotNull
    public static <
            AggregateT,
            EventIdT,
            EventT extends BaseEvent,
            SnapshotIdT,
            SnapshotT extends BaseSnapshot,
            QueryT extends BaseQuery,
            ExecutorT extends Executor
            > Flowable getForwardOnlyEvents(
            @NotNull List events,
            @NotNull ExecutorT executor,
            @NotNull Supplier>>> fallbackSnapshotAndEvents) {
        return executor.applyReverts(fromIterable(events))
                .toList()
                .map(Flowable::just)
                .onErrorReturn(throwable -> executor
                        .applyReverts(
                                fallbackSnapshotAndEvents.get()
                                        .flatMap(it -> fromIterable(it.getSecond()))
                        )
                        .toList().toFlowable()
                )
                .toFlowable()
                .flatMap(it -> it)
                .flatMap(Flowable::fromIterable);
    }

    /**
     * Turns a list of events into a readable log style string.
     *
     * @param events   The list of events
     * @param  The type of events
     *
     * @return A String representation of events
     */
    @NotNull
    public static  String stringify(
            @NotNull List events) {
        return events.stream()
                .map(EventT::toString)
                .collect(JOIN_EVENTS);
    }

    /**
     * Turns a list of events into a readable list of ids.
     *
     * @param events   The list of events
     * @param  The type of events
     *
     * @return A String representation of events
     */
    @NotNull
    public static  String ids(
            @NotNull List events) {
        return events.stream()
                .map(i -> String.valueOf(i.getId()))
                .collect(JOIN_EVENT_IDS);
    }

    /**
     * Sets the last event of a snapshot. Detects the kind of snapshot and sets required
     * properties appropriately
     *
     * @param snapshot The snapshot
     * @param event    The last event
     */
    public static void setLastEvent(@NotNull BaseSnapshot snapshot, @NotNull BaseEvent event) {
        if (snapshot instanceof VersionedSnapshot) {
            ((VersionedSnapshot) snapshot).setLastEventPosition(event.getPosition());
        }
        if (snapshot instanceof TemporalSnapshot) {
            ((TemporalSnapshot) snapshot).setLastEventTimestamp(event.getTimestamp());
        }
    }

    /**
     * Computes applicable events.
     *
     * @param            The aggregate over which the query executes
     * @param              The type of the EventT's id field
     * @param                The type of the Event
     * @param           The type of the SnapshotT's id field
     * @param             The type of the Snapshot
     * @param forwardOnlyEvents      Known forward only events
     * @param executor               An instance of Executor
     * @param snapshotAndEventsSince Events to use if forwardOnlyEvents is empty
     *
     * @return events that can be applied.
     */
    public static <
            AggregateT,
            EventIdT,
            EventT extends BaseEvent,
            SnapshotIdT,
            SnapshotT extends BaseSnapshot
            > Flowable getApplicableEvents(
            @NotNull Flowable forwardOnlyEvents,
            @NotNull Executor executor,
            @NotNull Supplier>>> snapshotAndEventsSince) {
        return forwardOnlyEvents
                .filter(e -> e instanceof Deprecates)
                .toList()
                .toFlowable()
                .flatMap(list -> list.isEmpty() ?
                        forwardOnlyEvents :
                        snapshotAndEventsSince.get().flatMap(p ->
                                getForwardOnlyEvents(p.getSecond(), executor, () ->
                                        error(new GroovesException(
                                                "Couldn't apply deprecates events")))
                        ));
    }

    private static final Logger logger = LoggerFactory.getLogger(Utils.class);

    /**
     * Computes a Flowable of a Pair of Snapshot and List of Events.
     *
     * @param reuseEarlierSnapshot         Whether earlier snapshots can be reused for this
     *                                     computation. It is generally a good idea to set this to
     *                                     true unless there are known reverts that demand this be
     *                                     set to false.
     * @param lastUsableSnapshot           Supplies the last usable snapshot
     * @param uncomputedEvents             Gets the list of uncomputed events for a snapshot
     * @param nonReusableSnapshotAndEvents Supplies a snapshot without reuse
     * @param emptySnapshot                Supplies an empty snapshot
     * @param                  The aggregate over which the query executes
     * @param                    The type of the EventT's id field
     * @param                      The type of the Event
     * @param                 The type of the SnapshotT's id field
     * @param                   The type of the Snapshot
     *
     * @return A flowable with one pair of snapshot and list of events.
     */
    @NotNull public static <
            AggregateT,
            EventIdT,
            EventT extends BaseEvent,
            SnapshotIdT,
            SnapshotT extends BaseSnapshot
            > Flowable>> getSnapshotsWithReuse(
            boolean reuseEarlierSnapshot,
            @NotNull Supplier> lastUsableSnapshot,
            @NotNull Function> uncomputedEvents,
            @NotNull Supplier>>> nonReusableSnapshotAndEvents,
            @NotNull Supplier emptySnapshot
    ) {
        if (reuseEarlierSnapshot) {
            return lastUsableSnapshot.get().flatMap(lastSnapshot ->
                    fromPublisher(uncomputedEvents.apply(lastSnapshot))
                            .toList()
                            .toFlowable()
                            .flatMap(events -> {
                                if (events.stream().anyMatch(it -> it instanceof RevertEvent)) {
                                    List reverts = events.stream()
                                            .filter(it -> it instanceof RevertEvent)
                                            .collect(toList());
                                    logger.info("     Uncomputed reverts exist: {}",
                                            stringify(reverts));
                                    return nonReusableSnapshotAndEvents.get();
                                } else {
                                    logger.debug("     Events since last snapshot: {}",
                                            stringify(events));
                                    return just(new Pair<>(lastSnapshot, events));
                                }
                            }));
        } else {
            SnapshotT lastSnapshot = emptySnapshot.get();

            final Flowable> uncomputedEventsF =
                    fromPublisher(uncomputedEvents.apply(lastSnapshot))
                            .toList()
                            .toFlowable();

            return uncomputedEventsF
                    .doOnNext(ue -> logger.debug("     Events since origin: {}", stringify(ue)))
                    .map(ue -> new Pair<>(lastSnapshot, ue));
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy