io.statusmachina.core.stdimpl.MachineInstanceImpl Maven / Gradle / Ivy
/*
* Copyright 2019 <---> Present Status Machina Contributors (https://github.com/entzik/status-machina/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package io.statusmachina.core.stdimpl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.statusmachina.core.api.*;
import io.statusmachina.core.spi.MachinePersistenceCallback;
import java.util.*;
import java.util.function.Consumer;
public class MachineInstanceImpl implements Machine {
private final String id;
private final MachineDefinition def;
private final ImmutableMap context;
private final ImmutableList> history;
private final Optional error;
private final S currentState;
private final MachinePersistenceCallback persistenceCallback;
public MachineInstanceImpl(
MachineDefinition def,
MachinePersistenceCallback persistenceCallback,
Map context
) throws Exception {
this(def, UUID.randomUUID().toString(), persistenceCallback, context);
}
public MachineInstanceImpl(
MachineDefinition def,
String id,
MachinePersistenceCallback persistenceCallback,
Map context
) throws Exception {
this(def, id, context, persistenceCallback);
}
public MachineInstanceImpl(
MachineDefinition def,
String id,
Map context, MachinePersistenceCallback persistenceCallback
) throws Exception {
this.def = def;
this.id = id;
this.history = ImmutableList.>builder().build();
this.currentState = def.getInitialState();
this.context = ImmutableMap.builder().putAll(context).build();
this.error = Optional.empty();
this.persistenceCallback = persistenceCallback;
persistenceCallback.runInTransaction(() -> persistenceCallback.saveNew(this));
}
public MachineInstanceImpl(
String id,
MachineDefinition def,
S currentState,
Map context,
List> history,
Optional error,
MachinePersistenceCallback persistenceCallback
) throws TransitionException {
this.def = def;
this.id = id;
this.history = ImmutableList.>builder().addAll(history).build();
this.error = error;
this.currentState = currentState;
this.context = ImmutableMap.builder().putAll(context).build();
this.persistenceCallback = persistenceCallback;
}
@Override
public String getId() {
return id;
}
@Override
public S getCurrentState() {
return currentState;
}
@Override
public ImmutableMap getContext() {
return ImmutableMap.builder().putAll(context).build();
}
@Override
public List> getHistory() {
return new ArrayList<>(history);
}
@Override
public Optional getError() {
return error;
}
@Override
public MachineDefinition getDefinition() {
return def;
}
@Override
public boolean isErrorState() {
return error.isPresent();
}
public Machine start() {
if (currentState.equals(def.getInitialState()))
return tryStp();
else
throw new IllegalStateException("machine is already started");
}
@Override
public Machine sendEvent(E event) throws TransitionException {
Transition transition = def.findEventTransion(currentState, event).orElseThrow(() -> new IllegalStateException("for machines of type " + def.getName() + " event " + event.toString() + " does not trigger any transition out of state " + currentState.toString()));
return applyTransition(transition, null);
}
@Override
public Machine sendEvent(E event, P param) throws TransitionException {
final Transition transition = def.findEventTransion(currentState, event).orElseThrow(() -> new IllegalStateException("for machines of type " + def.getName() + " event " + event.toString() + " does not trigger any transition out of state " + currentState.toString()));
return applyTransition(transition, param);
}
/*
@Override
public Machine sendEvent(E event) throws TransitionException {
return sendEvent(event);
}
@Override
public
Machine sendEvent(E event, P param) throws TransitionException {
return sendEvent(event, param);
}
*/
@Override
public Machine recoverFromError(S state, Map context) {
Optional newError = Optional.empty();
ImmutableMap newContext = ImmutableMap.builder().putAll(context).build();
return new MachineInstanceImpl(id, def, state, newContext, history, newError, persistenceCallback);
}
private Machine tryStp() throws TransitionException {
if (this.isErrorState())
return this;
else {
final Optional> stpTransition = def.findStpTransition(currentState);
if (stpTransition.isPresent()) {
return applyTransition(stpTransition.get(), null);
} else
return this;
}
}
private Machine applyTransition(Transition transition, P param) throws TransitionException {
try {
final Machine machine = persistenceCallback.runInTransaction(() -> {
final Optional> action = transition.getAction();
try {
ImmutableMap newContext = action.map(mapConsumer -> ((TransitionAction) mapConsumer).apply(context, param)).orElse(context);
Optional newError = Optional.empty();
S newState = transition.getTo();
final MachineInstanceImpl newMachine = new MachineInstanceImpl<>(id, def, newState, newContext, history, newError, persistenceCallback);
return persistenceCallback.update(newMachine);
} catch (Throwable t) {
def.getErrorHandler().accept(new DefaultErrorData<>(transition, param, t));
Optional newError = Optional.of(t.getMessage());
final MachineInstanceImpl newMachine = new MachineInstanceImpl<>(id, def, currentState, context, history, newError, persistenceCallback);
return persistenceCallback.update(newMachine);
}
});
if (!machine.isErrorState())
transition.getPostAction().ifPresent(transitionAction -> ((TransitionAction) transitionAction).apply(machine.getContext(), param));
return ((MachineInstanceImpl) machine).tryStp();
} catch (Exception e) {
throw new TransitionException(this, transition, e);
}
}
@Override
public boolean isTerminalState() {
return def.getTerminalStates().contains(currentState);
}
private static class VoidMachineConsumer implements Consumer> {
@Override
public void accept(Machine seMachine) {
}
}
private class StateAndContext {
private final S state;
private ImmutableMap context;
public StateAndContext(S state, ImmutableMap context) {
this.state = state;
this.context = context;
}
public S getState() {
return state;
}
public ImmutableMap getContext() {
return context;
}
}
private class DefaultErrorData implements ErrorData {
private final Transition transition;
private final P param;
private final Throwable t;
public DefaultErrorData(Transition transition, P param, Throwable t) {
this.transition = transition;
this.param = param;
this.t = t;
}
@Override
public S getFrom() {
return transition.getFrom();
}
@Override
public S getTo() {
return transition.getTo();
}
@Override
public Optional getEvent() {
return transition.getEvent();
}
@Override
public Map getContext() {
return new HashMap<>(context);
}
@Override
public P getEventParameter() {
return param;
}
@Override
public String getErrorMessage() {
return t.getMessage();
}
}
}