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

io.vlingo.lattice.model.stateful.StatefulEntity Maven / Gradle / Ivy

Go to download

Tooling for reactive Domain-Driven Design projects that are highly concurrent. Includes compute grid, actor caching, spaces, cross-node cluster messaging, CQRS, and Event Sourcing support.

There is a newer version: 1.7.5
Show newest version
// Copyright © 2012-2020 VLINGO LABS. All rights reserved.
//
// This Source Code Form is subject to the terms of the
// Mozilla Public License, v. 2.0. If a copy of the MPL
// was not distributed with this file, You can obtain
// one at https://mozilla.org/MPL/2.0/.

package io.vlingo.lattice.model.stateful;

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

import io.vlingo.actors.Actor;
import io.vlingo.common.Completes;
import io.vlingo.common.Outcome;
import io.vlingo.common.Tuple3;
import io.vlingo.lattice.model.CompletionSupplier;
import io.vlingo.lattice.model.stateful.StatefulTypeRegistry.Info;
import io.vlingo.symbio.Metadata;
import io.vlingo.symbio.Source;
import io.vlingo.symbio.store.Result;
import io.vlingo.symbio.store.StorageException;
import io.vlingo.symbio.store.state.StateStore.ReadResultInterest;
import io.vlingo.symbio.store.state.StateStore.WriteResultInterest;

/**
 * Abstract base for all entity types that require the id-clob/blob state storage
 * typical of a CQRS Command Model and CQRS Query Model. Therefore, extend me
 * for both your Command Model and CQRS Query Model, or for your CQRS Query Model
 * only when your Command Model uses the {@code EventSourced} or {@code EventSourced}.
 */
public abstract class StatefulEntity extends Actor
    implements ReadResultInterest, WriteResultInterest {

  private int currentVersion;
  private final Info info;
  private final ReadResultInterest readInterest;
  private final WriteResultInterest writeInterest;

  /**
   * Construct my default state.
   */
  protected StatefulEntity() {
    this.currentVersion = 0;
    this.info = info();
    this.readInterest = selfAs(ReadResultInterest.class);
    this.writeInterest = selfAs(WriteResultInterest.class);
  }

  /*
   * @see io.vlingo.actors.Actor#start()
   */
  @Override
  public void start() {
    super.start();

    final Tuple3>,String> newState = whenNewState();

    if (newState == null) {
      restore(true); // ignore not found (possible first time start)
    } else {
      apply(newState._1, newState._2, "", newState._3, null);
    }
  }

  /**
   * Received after the full asynchronous evaluation of each {@code apply()}.
   * Override if notification is desired.
   */
  protected void afterApply() { }

  /**
   * Answer my currentVersion, which, if zero, indicates that the
   * receiver is being initially constructed or reconstituted.
   * @return int
   */
  protected int currentVersion() {
    return currentVersion;
  }

  /**
   * Answer my unique identity, which much be provided by
   * my concrete extender by overriding.
   * @return String
   */
  protected abstract String id();

  /**
   * Answer a representation of a number of segments as a
   * composite id. The implementor of {@code id()} would use
   * this method if the its id is built from segments.
   * @param separator the String separator the insert between segments
   * @param idSegments the varargs String of one or more segments
   * @return String
   */
  protected String idFrom(final String separator, final String... idSegments) {
    final StringBuilder builder = new StringBuilder();
    builder.append(idSegments[0]);
    for (int idx = 1; idx < idSegments.length; ++idx) {
      builder.append(separator).append(idSegments[idx]);
    }
    return builder.toString();
  }

  /**
   * Answer {@code Completes}, applying my current {@code state} and {@code metadataValue}
   * that was modified due to the descriptive {@code operation}, along with {@code sources}, and
   * supply an eventual outcome by means of the given {@code andThen} function.
   * @param state the S typed state to apply
   * @param sources the {@code List} instances to apply
   * @param metadataValue the String metadata value to apply along with the state
   * @param operation the String descriptive name of the operation that caused the state modification
   * @param andThen the {@code Supplier} that will provide the fully updated state following this operation,
   * and which will used to answer an eventual outcome to the client of this entity
   * @param  the type of Source
   * @param  the return type of the Supplier function, which is the type of the completed state
   * @return {@code Completes}
   */
  protected  Completes apply(final S state, final List> sources, final String metadataValue, final String operation, final Supplier andThen) {
    final Metadata metadata = Metadata.with(state, metadataValue == null ? "" : metadataValue, operation == null ? "" : operation);
    stowMessages(WriteResultInterest.class);
    info.store.write(id(), state, nextVersion(), sources, metadata, writeInterest, CompletionSupplier.supplierOrNull(andThen, completesEventually()));
    return andThen == null ? null : completes();
  }

  /**
   * Answer {@code Completes}, applying my current {@code state} and {@code metadataValye}
   * that was modifieddue to the descriptive {@code operation} and supply an eventual outcome
   * by means of the given {@code andThen} function.
   * @param state the S typed state to apply
   * @param metadataValue the String metadata value to apply along with the state
   * @param operation the String descriptive name of the operation that caused the state modification
   * @param andThen the {@code Supplier} that will provide the fully updated state following this operation,
   * and which will used to answer an eventual outcome to the client of this entity
   * @param  the return type of the Supplier function, which is the type of the completed state
   * @return {@code Completes}
   */
  protected  Completes apply(final S state, final String metadataValue, final String operation, final Supplier andThen) {
    final Metadata metadata = Metadata.with(state, metadataValue == null ? "" : metadataValue, operation == null ? "" : operation);
    stowMessages(WriteResultInterest.class);
    info.store.write(id(), state, nextVersion(), metadata, writeInterest, CompletionSupplier.supplierOrNull(andThen, completesEventually()));
    return andThen == null ? null : completes();
  }

  /**
   * Answer {@code Completes}, applying my current {@code state} that was modified due to
   * the descriptive {@code operation} and supply an eventual outcome by means of the given
   * {@code andThen} function.
   * @param state the S typed state to apply
   * @param sources the {@code List} instances to apply
   * @param operation the String descriptive name of the operation that caused the state modification
   * @param andThen the {@code Supplier} that will provide the fully updated state following this operation,
   * and which will used to answer an eventual outcome to the client of this entity
   * @param  the type of Source
   * @param  the return type of the Supplier function, which is the type of the completed state
   * @return {@code Completes}
   */
  protected  Completes apply(final S state, final List> sources, final String operation, final Supplier andThen) {
    return apply(state, "", operation, andThen);
  }

  /**
   * Answer {@code Completes}, applying my current {@code state} that was modified due to
   * the descriptive {@code operation} and supply an eventual outcome by means of the given
   * {@code andThen} function.
   * @param state the S typed state to apply
   * @param operation the String descriptive name of the operation that caused the state modification
   * @param andThen the {@code Supplier} that will provide the fully updated state following this operation,
   * and which will used to answer an eventual outcome to the client of this entity
   * @param  the return type of the Supplier function, which is the type of the completed state
   * @return {@code Completes}
   */
  protected  Completes apply(final S state, final String operation, final Supplier andThen) {
    return apply(state, "", operation, andThen);
  }

  /**
   * Answer {@code Completes}, applying my current {@code state} and supply an eventual outcome by
   * means of the given {@code andThen} function.
   * @param state the S typed state to apply
   * @param sources the {@code List} instances to apply
   * @param andThen the {@code Supplier} that will provide the fully updated state following this operation,
   * and which will used to answer an eventual outcome to the client of this entity
   * @param  the type of Source
   * @param  the return type of the Supplier function, which is the type of the completed state
   * @return {@code Completes}
   */
  protected  Completes apply(final S state, final List> sources, final Supplier andThen) {
    return apply(state, sources, "", "", andThen);
  }

  /**
   * Answer {@code Completes}, applying my current {@code state} and supply an eventual outcome by
   * means of the given {@code andThen} function.
   * @param state the S typed state to apply
   * @param andThen the {@code Supplier} that will provide the fully updated state following this operation,
   * and which will used to answer an eventual outcome to the client of this entity
   * @param  the return type of the Supplier function, which is the type of the completed state
   * @return {@code Completes}
   */
  protected  Completes apply(final S state, final Supplier andThen) {
    return apply(state, "", "", andThen);
  }

  /**
   * Apply my current {@code state} and {@code metadataValye} that was modified
   * due to the descriptive {@code operation}.
   * @param state the S typed state to apply
   * @param sources the {@code List} instances to apply
   * @param metadataValue the String metadata value to apply along with the state
   * @param operation the String descriptive name of the operation that caused the state modification
   * @param  the type of Source
   */
  protected  void apply(final S state, final List> sources, final String metadataValue, final String operation) {
    apply(state, sources, metadataValue, operation, null);
  }

  /**
   * Apply my current {@code state} and {@code metadataValye} that was modified
   * due to the descriptive {@code operation}.
   * @param state the S typed state to apply
   * @param metadataValue the String metadata value to apply along with the state
   * @param operation the String descriptive name of the operation that caused the state modification
   */
  protected void apply(final S state, final String metadataValue, final String operation) {
    apply(state, metadataValue, operation, null);
  }

  /**
   * Apply my current {@code state} that was modified due to the descriptive {@code operation}.
   * @param state the S typed state to apply
   * @param sources the {@code List} instances to apply
   * @param operation the String descriptive name of the operation that caused the state modification
   * @param  the type of Source
   */
  protected  void apply(final S state, final List> sources, final String operation) {
    apply(state, "", operation, null);
  }

  /**
   * Apply my current {@code state} that was modified due to the descriptive {@code operation}.
   * @param state the S typed state to apply
   * @param operation the String descriptive name of the operation that caused the state modification
   */
  protected void apply(final S state, final String operation) {
    apply(state, "", operation, null);
  }

  /**
   * Apply my current {@code state} and {@code sources}.
   * @param state the S typed state to apply
   * @param sources the {@code List>} instances to apply
   * @param  the type of Source
   */
  protected  void apply(final S state, final List> sources) {
    apply(state, sources, "", "", null);
  }

  /**
   * Answer {@code Completes}, applying my current {@code state} and {@code sources}.
   * @param state the S typed state to apply
   * @param source the {@code Source} instances to apply
   * @param andThen the {@code Supplier} that will provide the fully updated state following this operation,
   * and which will used to answer an eventual outcome to the client of this entity
   * @param  the type of Source
   * @param  the return type of the Supplier function, which is the type of the completed state
   * @return {@code Completes}
   */
  protected  Completes apply(final S state, final Source source, final Supplier andThen) {
    return apply(state, Arrays.asList(source), "", "", andThen);
  }

  /**
   * Apply my current {@code state} and {@code source}.
   * @param state the S typed state to apply
   * @param source the {@code Source} instances to apply
   * @param  the type of Source
   */
  protected  void apply(final S state, final Source source) {
    apply(state, Arrays.asList(source), "", "", null);
  }

  /**
   * Apply my current {@code state}.
   * @param state the S typed state to apply
   */
  protected void apply(final S state) {
    apply(state, "", "", null);
  }

  /**
   * Answer my new {@code state}, {@code sources}, and {@code operation} as a
   * {@code Tuple3>,String>}, or {@code null} if not new.
   * Used each time I am started to determine whether restoration is necessary
   * or otherwise initial state persistence. By default I always attempt to
   * restore my state while ignoring non-existence.
   * @param  the type of Source
   * @return {@code Tuple3>,String>}
   */
  protected  Tuple3>,String> whenNewState() {
    return null;
  }

  /**
   * Restore my current state, dispatching to {@code state(final S state)} when completed.
   */
  protected void restore() {
    restore(false);
  }

  /**
   * Received by my extender when my current state has been applied and restored.
   * Must be overridden by my extender.
   * @param state the S typed state
   */
  protected abstract void state(final S state);

  /**
   * Received by my extender when I must know it's state type.
   * Must be overridden by my extender.
   * @return {@code Class}
   */
  protected abstract Class stateType();

  //=====================================
  // FOR INTERNAL USE ONLY.
  //=====================================

  /*
   * @see io.vlingo.symbio.store.state.StateStore.ReadResultInterest#readResultedIn(io.vlingo.common.Outcome, java.lang.String, java.lang.Object, int, io.vlingo.symbio.Metadata, java.lang.Object)
   */
  @Override
  @SuppressWarnings("unchecked")
  final public  void readResultedIn(final Outcome outcome, final String id, final ST state, final int stateVersion, final Metadata metadata, final Object object) {
    outcome
      .andThen(result -> {
        state((S) state);
        currentVersion = stateVersion;
        disperseStowedMessages();
        return result;
      })
      .otherwise(cause -> {
        disperseStowedMessages();
        final boolean ignoreNotFound = (boolean) object;
        if (!ignoreNotFound) {
          final String message = "State not restored for: " + getClass() + "(" + id + ") because: " + cause.result + " with: " + cause.getMessage();
          logger().error(message, cause);
          throw new IllegalStateException(message, cause);
        }
        return cause.result;
      });
  }

  /*
   * @see io.vlingo.symbio.store.state.StateStore.WriteResultInterest#writeResultedIn(io.vlingo.common.Outcome, java.lang.String, java.lang.Object, int, java.lang.Object)
   */
  @Override
  @SuppressWarnings("unchecked")
  final public  void writeResultedIn(final Outcome outcome, final String id, final ST state, final int stateVersion, final List> sources, final Object supplier) {
    outcome
      .andThen(result -> {
        state((S) state);
        currentVersion = stateVersion;
        afterApply();
        completeUsing(supplier);
        disperseStowedMessages();
        return result;
      })
      .otherwise(cause -> {
        disperseStowedMessages();
        final String message = "State not applied for: " + getClass() + "(" + id + ") because: " + cause.result + " with: " + cause.getMessage();
        logger().error(message, cause);
        throw new IllegalStateException(message, cause);
      });
  }

  /**
   * Dispatches to the {@code supplier} to complete my protocol
   * message that answers an eventual outcome.
   * @param supplier the Object that is cast to a {@code CompletionSupplier} and then completed
   */
  private void completeUsing(final Object supplier) {
    if (supplier != null) {
      ((CompletionSupplier) supplier).complete();
    }
  }

  /**
   * Answer the {@code Info} for my type.
   * @return {@code Info}
   */
  private Info info() {
    try {
      return stage().world().resolveDynamic(StatefulTypeRegistry.INTERNAL_NAME, StatefulTypeRegistry.class).info(stateType());
    } catch (Exception e) {
      final String message = getClass().getSimpleName() + ": Info not registered with StatefulTypeRegistry.";
      logger().error(message);
      throw new IllegalStateException(message);
    }
  }

  /**
   * Answer my {@code nextVersion}, which is one greater than my {@code currentVersion}.
   * @return int
   */
  private int nextVersion() {
    return currentVersion + 1;
  }

  /**
   * Cause state restoration and indicate whether a not found
   * condition can be safely ignored.
   * @param ignoreNotFound the boolean indicating whether or not a not found condition may be ignored
   */
  private void restore(final boolean ignoreNotFound) {
    stowMessages(ReadResultInterest.class);
    info.store.read(id(), info.storeType, readInterest, ignoreNotFound);
  }
}