io.vlingo.actors.Actor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vlingo-actors Show documentation
Show all versions of vlingo-actors Show documentation
Type safe Actor Model toolkit for reactive concurrency and resiliency using Java and other JVM languages.
// 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.actors;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import io.vlingo.actors.testkit.TestContext;
import io.vlingo.actors.testkit.TestEnvironment;
import io.vlingo.actors.testkit.TestState;
import io.vlingo.actors.testkit.TestStateView;
import io.vlingo.common.Completes;
import io.vlingo.common.Scheduler;
/**
* The abstract base class of all concrete {@code Actor} types. This base provides common
* facilities and life cycle processing for all {@code Actor} types.
*/
public abstract class Actor implements Startable, Stoppable, Relocatable, TestStateView {
final ResultReturns returns;
final LifeCycle lifeCycle;
/**
* Answers the {@code address} of this {@code Actor}.
* @return Address
*/
public Address address() {
return lifeCycle.address();
}
/**
* @see io.vlingo.actors.Stoppable#conclude()
*/
@Override
public void conclude() {
selfAs(Stoppable.class).stop();
}
/**
* Answers the {@code DeadLetters} for this {@code Actor}.
* @return DeadLetters
*/
public DeadLetters deadLetters() {
return lifeCycle.environment.stage.world().deadLetters();
}
/**
* Answers the {@code Scheduler} for this {@code Actor}.
* @return Scheduler
*/
public Scheduler scheduler() {
return lifeCycle.environment.stage.scheduler();
}
/**
* @see io.vlingo.actors.Relocatable#stateSnapshot(java.lang.Object)
*/
@Override
public void stateSnapshot(final S stateSnapshot) {
// no-op
}
/**
* @see io.vlingo.actors.Relocatable#stateSnapshot()
*/
@Override
public S stateSnapshot() {
return null; // no-op
}
/**
* The default implementation of {@code start()}, which is a no-op. Override if needed.
*
* @see io.vlingo.actors.Startable#start()
*/
@Override
public void start() {
}
/**
* Answers whether or not this {@code Actor} has been stopped or is in the process or stopping.
* @return boolean
*
* @see io.vlingo.actors.Stoppable#isStopped()
*/
@Override
public boolean isStopped() {
return lifeCycle.isStopped();
}
/**
* Initiates the process of stopping this {@code Actor} and all of its children.
*
* @see io.vlingo.actors.Stoppable#stop()
*/
@Override
public void stop() {
if (!isStopped()) {
if (lifeCycle.address().id() != World.DEADLETTERS_ID) {
// TODO: remove this actor as a child on parent
lifeCycle.suspendForStop();
lifeCycle.stop(this);
}
}
}
/**
* Received from the surrounding {@code testkit.TestActor} to indicate
* that it is in use, enabling any special test initialization as
* needed. This is received (1) with a {@code TestContext} when the
* {@code TestActor} is first constructed, and (2) with {@code null}
* before each {@code TestMailbox} delivery.
* @param context the TestContext
*/
public void viewTestStateInitialization(final TestContext context) {
// override for concrete actor test signaling
}
/**
* Answers the {@code TestState} for this {@code Actor}. Override to provide a snapshot of the current {@code Actor} state.
* @return TestState
*/
@Override
public TestState viewTestState() {
// override for concrete actor state
return new TestState();
}
/**
* Answers whether or not this {@code Actor} is equal to {@code other}.
* @param other the {@code Object} to which this {@code Actor} is compared
* @return boolean
*/
@Override
public boolean equals(final Object other) {
if (other == null || other.getClass() != this.getClass()) {
return false;
}
return address().equals(((Actor) other).lifeCycle.address());
}
/**
* Answers the {@code int} hash code of this {@code Actor}.
* @return int
*/
@Override
public int hashCode() {
return lifeCycle.hashCode();
}
/**
* Answers the {@code String} representation of this {@code Actor}.
* @return String
*/
@Override
public String toString() {
return "Actor[type=" + this.getClass().getSimpleName() + " address=" + address() + "]";
}
/**
* Answers the parent {@code Actor} of this {@code Actor}. (INTERNAL ONLY)
* @return Actor
*/
Actor parent() {
if (lifeCycle.environment.isSecured()) {
throw new IllegalStateException("A secured actor cannot provide its parent.");
}
return lifeCycle.environment.parent;
}
/**
* Initializes the newly created {@code Actor}.
*/
protected Actor() {
final Environment maybeEnvironment = ActorFactory.threadLocalEnvironment.get();
this.lifeCycle = new LifeCycle(maybeEnvironment != null ? maybeEnvironment : new TestEnvironment(), new Evictable(this));
ActorFactory.threadLocalEnvironment.set(null);
this.returns = new ResultReturns();
}
/**
* Answer my internal {@code Completes} from {@code completes()} after preparing
* for the {@code eventualOutcome} to be set in my {@code completesEventually()}.
* @param eventualOutcome the {@code Completes} the provides an eventual outcome
* @param the type of eventual outcome
* @return {@code Completes}
*/
protected Completes answerFrom(final Completes eventualOutcome) {
eventualOutcome.andFinallyConsume((R value) -> completesEventually().with(value));
return completes();
}
/**
* Answers the {@code T} protocol for the child {@code Actor} to be created by this parent {@code Actor}.
* @param the protocol type
* @param protocol the {@code Class} protocol of the child {@code Actor}
* @param definition the {@code Definition} of the child {@code Actor} to be created by this parent {@code Actor}
* @return T
*/
protected T childActorFor(final Class protocol, final Definition definition) {
if (definition.supervisor() != null) {
return lifeCycle.environment.stage.actorFor(protocol, definition, this, definition.supervisor(), logger());
} else {
if (this instanceof Supervisor) {
return lifeCycle.environment.stage.actorFor(protocol, definition, this, lifeCycle.lookUpProxy(Supervisor.class), logger());
} else {
return lifeCycle.environment.stage.actorFor(protocol, definition, this, null, logger());
}
}
}
/**
* Answers the {@code Protocols} for the child {@code Actor} to be created by this parent {@code Actor}.
* @param protocols the {@code Class[]} protocols of the child {@code Actor}
* @param definition the {@code Definition} of the child {@code Actor} to be created by this parent {@code Actor}
* @return Protocols
*/
protected Protocols childActorFor(final Class>[] protocols, final Definition definition) {
if (definition.supervisor() != null) {
return lifeCycle.environment.stage.actorFor(protocols, definition, this, definition.supervisor(), logger());
} else {
if (this instanceof Supervisor) {
return lifeCycle.environment.stage.actorFor(protocols, definition, this, lifeCycle.lookUpProxy(Supervisor.class), logger());
} else {
return lifeCycle.environment.stage.actorFor(protocols, definition, this, null, logger());
}
}
}
/**
* Answers the {@code Completes} instance for this {@code Actor}, or throws {@code IllegalStateException} if the behavior of the currently
* delivered {@code Message} does not answer a {@code Completes}.
* @param the protocol type
* @return {@code Completes}
*/
@SuppressWarnings("unchecked")
protected Completes completes() {
if (returns == null || returns.__internal__clientReturns == null || !returns.__internal__clientReturns.isCompletes()) {
throw new IllegalStateException("Completes is not available for this protocol behavior; return type must be Completes.");
}
return (Completes) returns;
}
/**
* Answers the {@code CompletableFuture} instance for this {@code Actor}, or throws {@code IllegalStateException} if the behavior of the currently
* delivered {@code Message} does not answer a {@code CompletableFuture}.
* @param the protocol type
* @return {@code CompletableFuture}
*/
@SuppressWarnings("unchecked")
protected CompletableFuture completableFuture() {
if (returns == null || returns.__internal__clientReturns == null || !returns.__internal__clientReturns.isCompletableFuture()) {
throw new IllegalStateException("CompletableFuture is not available for this protocol behavior; return type must be CompletableFuture.");
}
return (CompletableFuture) returns.asCompletableFuture();
}
/**
* Answers the {@code Future} instance for this {@code Actor}, or throws {@code IllegalStateException} if the behavior of the currently
* delivered {@code Message} does not answer a {@code Future}.
* @param the protocol type
* @param callable the {@code Callable} producing the outcome for the {@code Future} to answer
* @return {@code Future}
*/
@SuppressWarnings("unchecked")
protected Future future(final Callable callable) {
if (returns == null || returns.__internal__clientReturns == null || !returns.__internal__clientReturns.isFuture()) {
throw new IllegalStateException("Future is not available for this protocol behavior; return type must be Future.");
}
final CompletableFuture completableFuture = (CompletableFuture) returns.clientReturns().asFuture();
try {
final T outcome = callable.call();
returns.__internal__outcome = outcome;
completableFuture.complete(outcome);
} catch (Exception e) {
throw new RuntimeException("Actor method returning Future failed.", e);
}
return completableFuture;
}
/**
* Answers a {@code CompletesEventually} if the behavior of the currently
* delivered {@code Message} does answers a {@code Completes}. Otherwise the outcome
* is undefined.
* @return CompletesEventually
*/
protected CompletesEventually completesEventually() {
return lifeCycle.environment.completesEventually(returns);
}
/**
* Answers the {@code Definition} of this {@code Actor}.
* @return Definition
*/
public Definition definition() {
return lifeCycle.definition();
}
/**
* Answers the {@code Logger} of this {@code Actor}.
* @return Logger
*/
protected Logger logger() {
return lifeCycle.environment.logger;
}
/**
* Answers the parent of this {@code Actor} as the {@code T} protocol.
* @param protocol the {@code Class} of the protocol
* @param the protocol type
* @return T
*/
protected T parentAs(final Class protocol) {
if (lifeCycle.environment.isSecured()) {
throw new IllegalStateException("A secured actor cannot provide its parent.");
}
final Actor parent = lifeCycle.environment.parent;
return lifeCycle.environment.stage.actorProxyFor(protocol, parent, parent.lifeCycle.environment.mailbox);
}
/**
* Secures this {@code Actor}. (INTERNAL ONLY)
*/
protected void secure() {
lifeCycle.secure();
}
/**
* Answers this {@code Actor} as a {@code T} protocol. This {@code Actor} must implement the {@code Class} protocol.
* @param protocol the {@code Class} protocol
* @param the protocol type
* @return T
*/
protected T selfAs(final Class protocol) {
return lifeCycle.environment.stage.actorProxyFor(protocol, this, lifeCycle.environment.mailbox);
}
/**
* Answers the {@code Stage} of this {@code Actor}.
* @return Stage
*/
protected final Stage stage() {
if (lifeCycle.environment.isSecured()) {
throw new IllegalStateException("A secured actor cannot provide its stage.");
}
return lifeCycle.environment.stage;
}
/**
* Answers the {@code Stage} of the given name.
* @param name the {@code String} name of the {@code Stage} to find
* @return Stage
*/
protected Stage stageNamed(final String name) {
return lifeCycle.environment.stage.world().stageNamed(name);
}
//=======================================
// stowing/dispersing
//=======================================
/**
* Starts the process of dispersing any messages stowed for this {@code Actor}.
*/
protected void disperseStowedMessages() {
lifeCycle.environment.mailbox.resume(Mailbox.Paused);
}
/**
* Starts the process of stowing messages for this {@code Actor}, and registers {@code stowageOverrides} as
* the protocol that will trigger dispersal.
* @param stowageOverrides the {@code Class} protocol that will trigger dispersal
*/
protected void stowMessages(final Class>... stowageOverrides) {
lifeCycle.environment.mailbox.suspendExceptFor(Mailbox.Paused, stowageOverrides);
}
//=======================================
// life cycle overrides
//=======================================
/**
* The message delivered before the {@code Actor} has fully started. Override to implement.
*/
protected void beforeStart() {
// override
}
/**
* The message delivered after the {@code Actor} has fully stopped. Override to implement.
*/
protected void afterStop() {
// override
}
/**
* The message delivered before the {@code Actor} has been restarted by its supervisor due to an exception.
* Override to implement.
* @param reason the {@code Throwable} cause of the supervision restart
*/
protected void beforeRestart(final Throwable reason) {
// override for specific recovery
logger().error("Default before restart recovery after: " + reason.getMessage(), reason);
lifeCycle.afterStop(this);
}
/**
* The message delivered after the {@code Actor} has been restarted by its supervisor due to an exception.
* Override to implement.
* @param reason the {@code Throwable} cause of the supervision restart
*/
protected void afterRestart(final Throwable reason) {
// override for specific recovery
logger().error("Default after restart recovery after: " + reason.getMessage(), reason);
lifeCycle.beforeStart(this);
}
/**
* The message delivered before the {@code Actor} has been resumed by its supervisor due to an exception.
* Override to implement.
* @param reason the {@code Throwable} cause of the supervision resume
*/
protected void beforeResume(final Throwable reason) {
// override for specific recovery
logger().error("Default before resume recovery after: " + reason.getMessage(), reason);
}
}