package com.sequenceiq.cloudbreak.core.flow2.config;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.Message;
import org.springframework.statemachine.config.ObjectStateMachineFactory;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineConfigurationBuilder;
import org.springframework.statemachine.config.builders.StateMachineStateBuilder;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.StateConfigurer;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import com.sequenceiq.cloudbreak.core.flow2.AbstractAction;
import com.sequenceiq.cloudbreak.core.flow2.DefaultFlowTriggerCondition;
import com.sequenceiq.cloudbreak.core.flow2.restart.DefaultRestartAction;
import com.sequenceiq.cloudbreak.core.flow2.EventConverterAdapter;
import com.sequenceiq.cloudbreak.core.flow2.Flow;
import com.sequenceiq.cloudbreak.core.flow2.FlowAdapter;
import com.sequenceiq.cloudbreak.core.flow2.FlowEvent;
import com.sequenceiq.cloudbreak.core.flow2.FlowFinalizeAction;
import com.sequenceiq.cloudbreak.core.flow2.FlowState;
import com.sequenceiq.cloudbreak.core.flow2.FlowTriggerCondition;
import com.sequenceiq.cloudbreak.core.flow2.MessageFactory;
import com.sequenceiq.cloudbreak.core.flow2.RestartAction;
import com.sequenceiq.cloudbreak.core.flow2.StateConverterAdapter;
public abstract class AbstractFlowConfiguration<S extends FlowState, E extends FlowEvent> implements FlowConfiguration<E> {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractFlowConfiguration.class);
private StateMachineFactory<S, E> stateMachineFactory;
private final Class<S> stateType;
private final Class<E> eventType;
@Inject
private ApplicationContext applicationContext;
@Inject
@Qualifier("DefaultRestartAction")
private DefaultRestartAction defaultRestartAction;
public AbstractFlowConfiguration(Class<S> stateType, Class<E> eventType) {
this.stateType = stateType;
this.eventType = eventType;
}
@PostConstruct
public void init() throws Exception {
MachineConfiguration<S, E> config = getStateMachineConfiguration();
config.configurationBuilder.withConfiguration().listener(config.listener).taskExecutor(config.executor);
configure(config.stateBuilder, config.transitionBuilder, getEdgeConfig(), getTransitions());
stateMachineFactory = new ObjectStateMachineFactory<>(config.configurationBuilder.build(), config.transitionBuilder.build(),
config.stateBuilder.build());
}
@Override
public Flow createFlow(String flowId) {
return new FlowAdapter<>(flowId, getStateMachineFactory().getStateMachine(), new MessageFactory<E>(), new StateConverterAdapter<>(stateType),
new EventConverterAdapter<>(eventType), getClass());
}
@Override
public FlowTriggerCondition getFlowTriggerCondition() {
return applicationContext.getBean(DefaultFlowTriggerCondition.class);
}
protected StateMachineFactory<S, E> getStateMachineFactory() {
return stateMachineFactory;
}
protected ApplicationContext getApplicationContext() {
return applicationContext;
}
private void configure(StateMachineStateConfigurer<S, E> stateConfig, StateMachineTransitionConfigurer<S, E> transitionConfig,
FlowEdgeConfig<S, E> flowEdgeConfig, List<Transition<S, E>> transitions) throws Exception {
StateConfigurer<S, E> stateConfigurer = stateConfig.withStates().initial(flowEdgeConfig.initState).end(flowEdgeConfig.finalState);
ExternalTransitionConfigurer<S, E> transitionConfigurer = null;
Set<S> failHandled = new HashSet<>();
for (Transition<S, E> transition : transitions) {
transitionConfigurer = transitionConfigurer == null ? transitionConfig.withExternal() : transitionConfigurer.and().withExternal();
AbstractAction<S, E, ?, ?> action = getAction(transition.source);
if (action != null) {
stateConfigurer.state(transition.source, action, null);
}
transitionConfigurer.source(transition.source).target(transition.target).event(transition.event);
if (action != null && transition.getFailureEvent() != null && transition.target != flowEdgeConfig.defaultFailureState) {
action.setFailureEvent(transition.getFailureEvent());
S failureState = Optional.ofNullable(transition.getFailureState()).orElse(flowEdgeConfig.defaultFailureState);
stateConfigurer.state(failureState, getAction(failureState), null);
transitionConfigurer.and().withExternal().source(transition.source).target(failureState).event(transition.getFailureEvent());
if (!failHandled.contains(failureState)) {
failHandled.add(failureState);
transitionConfigurer.and().withExternal().source(failureState).target(flowEdgeConfig.finalState).event(flowEdgeConfig.failureHandled);
}
}
}
stateConfigurer.state(flowEdgeConfig.finalState, getAction(FlowFinalizeAction.class), null);
}
protected MachineConfiguration<S, E> getStateMachineConfiguration() {
StateMachineConfigurationBuilder<S, E> configurationBuilder =
new StateMachineConfigurationBuilder<>(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true);
StateMachineStateBuilder<S, E> stateBuilder =
new StateMachineStateBuilder<>(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true);
StateMachineTransitionBuilder<S, E> transitionBuilder =
new StateMachineTransitionBuilder<>(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true);
StateMachineListener<S, E> listener =
new StateMachineListenerAdapter<S, E>() {
@Override
public void stateChanged(State<S, E> from, State<S, E> to) {
LOGGER.debug("state changed from {} to {}", from, to);
}
@Override
public void eventNotAccepted(Message<E> event) {
LOGGER.error("{} not accepted event: {}", getClass().getSimpleName(), event);
}
};
return new MachineConfiguration<>(configurationBuilder, stateBuilder, transitionBuilder, listener, new SyncTaskExecutor());
}
protected abstract List<Transition<S, E>> getTransitions();
protected abstract FlowEdgeConfig<S, E> getEdgeConfig();
private AbstractAction getAction(FlowState state) {
return state.action() == null ? getAction(state.name()) : getAction(state.action());
}
private AbstractAction getAction(Class<? extends AbstractAction> clazz) {
return applicationContext.getBean(clazz.getSimpleName(), clazz);
}
private AbstractAction getAction(String name) {
try {
return applicationContext.getBean(name, AbstractAction.class);
} catch (NoSuchBeanDefinitionException ex) {
return null;
}
}
@Override
public RestartAction getRestartAction(final String event) {
Optional<Transition<S, E>> transaction = getTransitions().stream().filter(t -> t.event.event().equals(event)).findFirst();
if (transaction.isPresent() && transaction.get().target.restartAction() != null) {
Class<? extends RestartAction> restartAction = transaction.get().target.restartAction();
return applicationContext.getBean(restartAction.getSimpleName(), restartAction);
}
return defaultRestartAction;
}
static class MachineConfiguration<S, E> {
private final StateMachineConfigurationBuilder<S, E> configurationBuilder;
private final StateMachineStateBuilder<S, E> stateBuilder;
private final StateMachineTransitionBuilder<S, E> transitionBuilder;
private final StateMachineListener<S, E> listener;
private final TaskExecutor executor;
MachineConfiguration(StateMachineConfigurationBuilder<S, E> configurationBuilder, StateMachineStateBuilder<S, E> stateBuilder,
StateMachineTransitionBuilder<S, E> transitionBuilder, StateMachineListener<S, E> listener, TaskExecutor executor) {
this.configurationBuilder = configurationBuilder;
this.stateBuilder = stateBuilder;
this.transitionBuilder = transitionBuilder;
this.listener = listener;
this.executor = executor;
}
}
protected static class Transition<S extends FlowState, E extends FlowEvent> {
private final S source;
private final S target;
private final E event;
private final S failureState;
private final E failureEvent;
private Transition(S source, S target, E event, S failureState, E failureEvent) {
this.source = source;
this.target = target;
this.event = event;
this.failureState = failureState;
this.failureEvent = failureEvent;
}
private S getFailureState() {
return failureState;
}
private E getFailureEvent() {
return failureEvent;
}
public static class Builder<S extends FlowState, E extends FlowEvent> {
private List<Transition<S, E>> transitions = new ArrayList<>();
private Optional<E> defaultFailureEvent = Optional.empty();
public ToBuilder<S, E> from(S from) {
return new ToBuilder<>(from, this);
}
public void addTransition(S from, S to, E with, S fail) {
if (!defaultFailureEvent.isPresent()) {
throw new UnsupportedOperationException("Default failureEvent event must specified!");
}
addTransition(from, to, with, fail, defaultFailureEvent.get());
}
public void addTransition(S from, S to, E with, S fail, E withFailure) {
transitions.add(new Transition<>(from, to, with, fail, withFailure));
}
public void addTransition(S from, S to, E with) {
transitions.add(new Transition<>(from, to, with, null, null));
}
public List<Transition<S, E>> build() {
return transitions;
}
public Builder<S, E> defaultFailureEvent(E defaultFailureEvent) {
this.defaultFailureEvent = Optional.of(defaultFailureEvent);
return this;
}
}
public static class ToBuilder<S extends FlowState, E extends FlowEvent> {
private final S from;
private final Builder<S, E> builder;
ToBuilder(S from, Builder<S, E> b) {
this.from = from;
this.builder = b;
}
public WithBuilder<S, E> to(S to) {
return new WithBuilder<>(from, to, builder);
}
}
public static class WithBuilder<S extends FlowState, E extends FlowEvent> {
private final S from;
private final S to;
private final Builder<S, E> builder;
WithBuilder(S from, S to, Builder<S, E> b) {
this.from = from;
this.to = to;
this.builder = b;
}
public FailureBuilder<S, E> event(E with) {
return new FailureBuilder<>(from, to, with, builder);
}
}
public static class FailureBuilder<S extends FlowState, E extends FlowEvent> {
private final S from;
private final S to;
private final E with;
private final Builder<S, E> builder;
private S failure;
FailureBuilder(S from, S to, E with, Builder<S, E> b) {
this.from = from;
this.to = to;
this.with = with;
this.builder = b;
}
public FailureBuilder<S, E> failureState(S toFailure) {
failure = toFailure;
return this;
}
public Builder<S, E> failureEvent(E withFailure) {
builder.addTransition(from, to, with, failure, withFailure);
return builder;
}
public Builder<S, E> defaultFailureEvent() {
builder.addTransition(from, to, with, failure);
return builder;
}
public Builder<S, E> noFailureEvent() {
builder.addTransition(from, to, with);
return builder;
}
}
}
protected static class FlowEdgeConfig<S, E> {
private final S initState;
private final S finalState;
private final S defaultFailureState;
private final E failureHandled;
public FlowEdgeConfig(S initState, S finalState, S defaultFailureState, E failureHandled) {
this.initState = initState;
this.finalState = finalState;
this.defaultFailureState = defaultFailureState;
this.failureHandled = failureHandled;
}
}
}