/*
* Copyright 2016 the original author or authors.
*
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.springframework.statemachine.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.action.SpelExpressionAction;
import org.springframework.statemachine.config.model.AbstractStateMachineModelFactory;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.ConfigurationData;
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
import org.springframework.statemachine.config.model.HistoryData;
import org.springframework.statemachine.config.model.JunctionData;
import org.springframework.statemachine.config.model.StateData;
import org.springframework.statemachine.config.model.StateMachineModel;
import org.springframework.statemachine.config.model.StateMachineModelFactory;
import org.springframework.statemachine.config.model.StatesData;
import org.springframework.statemachine.config.model.TransitionData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.guard.SpelExpressionGuard;
import org.springframework.statemachine.state.PseudoStateKind;
import org.springframework.statemachine.transition.TransitionKind;
import org.springframework.util.StringUtils;
/**
* A generic {@link StateMachineModelFactory} which is backed by a Spring Data
* Repository abstraction.
*
* @author Janne Valkealahti
*
*/
public class RepositoryStateMachineModelFactory extends AbstractStateMachineModelFactory<String, String>
implements StateMachineModelFactory<String, String> {
private final StateRepository<? extends RepositoryState> stateRepository;
private final TransitionRepository<? extends RepositoryTransition> transitionRepository;
/**
* Instantiates a new repository state machine model factory.
*
* @param stateRepository the state repository
* @param transitionRepository the transition repository
*/
public RepositoryStateMachineModelFactory(StateRepository<? extends RepositoryState> stateRepository,
TransitionRepository<? extends RepositoryTransition> transitionRepository) {
this.stateRepository = stateRepository;
this.transitionRepository = transitionRepository;
}
@Override
public StateMachineModel<String, String> build() {
return build(null);
}
@Override
public StateMachineModel<String, String> build(String machineId) {
ConfigurationData<String, String> configurationData = new ConfigurationData<>();
Collection<StateData<String, String>> stateDatas = new ArrayList<>();
for (RepositoryState s : stateRepository.findByMachineId(machineId == null ? "" : machineId)) {
// do recursive build to get states for a submachine
StateMachineModel<String, String> subStateMachineModel = null;
String submachineId = s.getSubmachineId();
if (submachineId != null) {
subStateMachineModel = build(submachineId);
}
Collection<Action<String, String>> stateActions = new ArrayList<Action<String, String>>();
Set<? extends RepositoryAction> repositoryStateActions = s.getStateActions();
if (repositoryStateActions != null) {
for (RepositoryAction repositoryAction : repositoryStateActions) {
Action<String, String> action = null;
if (StringUtils.hasText(repositoryAction.getName())) {
action = resolveAction(repositoryAction.getName());
} else if (StringUtils.hasText(repositoryAction.getSpel())) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
action = new SpelExpressionAction<String, String>(parser.parseExpression(repositoryAction.getSpel()));
}
if (action != null) {
stateActions.add(action);
}
}
}
Collection<Action<String, String>> entryActions = new ArrayList<Action<String, String>>();
Set<? extends RepositoryAction> repositoryEntryActions = s.getEntryActions();
if (repositoryEntryActions != null) {
for (RepositoryAction repositoryAction : repositoryEntryActions) {
Action<String, String> action = null;
if (StringUtils.hasText(repositoryAction.getName())) {
action = resolveAction(repositoryAction.getName());
} else if (StringUtils.hasText(repositoryAction.getSpel())) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
action = new SpelExpressionAction<String, String>(parser.parseExpression(repositoryAction.getSpel()));
}
if (action != null) {
stateActions.add(action);
}
}
}
Collection<Action<String, String>> exitActions = new ArrayList<Action<String, String>>();
Set<? extends RepositoryAction> repositoryExitActions = s.getExitActions();
if (repositoryExitActions != null) {
for (RepositoryAction repositoryAction : repositoryExitActions) {
Action<String, String> action = null;
if (StringUtils.hasText(repositoryAction.getName())) {
action = resolveAction(repositoryAction.getName());
} else if (StringUtils.hasText(repositoryAction.getSpel())) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
action = new SpelExpressionAction<String, String>(parser.parseExpression(repositoryAction.getSpel()));
}
if (action != null) {
stateActions.add(action);
}
}
}
RepositoryState parentState = s.getParentState();
Object region = s.getRegion();
StateData<String, String> stateData = new StateData<String, String>(parentState != null ? parentState.getState() : null, region,
s.getState(), s.isInitial());
Action<String, String> initialAction = null;
if (s.getInitialAction() != null) {
if (StringUtils.hasText(s.getInitialAction().getName())) {
initialAction = resolveAction(s.getInitialAction().getName());
} else if (StringUtils.hasText(s.getInitialAction().getSpel())) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
initialAction = new SpelExpressionAction<String, String>(parser.parseExpression(s.getInitialAction().getSpel()));
}
}
stateData.setInitialAction(initialAction);
stateData.setStateActions(stateActions);
stateData.setEntryActions(entryActions);
stateData.setExitActions(exitActions);
if (s.getKind() != null) {
stateData.setPseudoStateKind(s.getKind());
if (s.getKind() == PseudoStateKind.END) {
stateData.setEnd(true);
}
}
stateData.setDeferred(s.getDeferredEvents());
if (subStateMachineModel != null) {
// copy are set parent as state we're currently on
Collection<StateData<String, String>> submachineStateData = new ArrayList<>();
Collection<StateData<String, String>> submachineStateDataOrig = subStateMachineModel.getStatesData().getStateData();
for (StateData<String, String> sd : submachineStateDataOrig) {
submachineStateData.add(new StateData<String, String>(s.getState(), sd.getRegion(), sd.getState(),
sd.getDeferred(), sd.getEntryActions(), sd.getExitActions(), sd.isInitial(), sd.getInitialAction()));
}
stateData.setSubmachineStateData(submachineStateData);
}
stateDatas.add(stateData);
}
StatesData<String, String> statesData = new StatesData<>(stateDatas);
Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
Collection<EntryData<String, String>> entrys = new ArrayList<EntryData<String, String>>();
Collection<ExitData<String, String>> exits = new ArrayList<ExitData<String, String>>();
Collection<HistoryData<String, String>> historys = new ArrayList<HistoryData<String, String>>();
Map<String, LinkedList<ChoiceData<String, String>>> choices = new HashMap<String, LinkedList<ChoiceData<String,String>>>();
Map<String, LinkedList<JunctionData<String, String>>> junctions = new HashMap<String, LinkedList<JunctionData<String,String>>>();
Map<String, List<String>> forks = new HashMap<String, List<String>>();
Map<String, List<String>> joins = new HashMap<String, List<String>>();
for (RepositoryTransition t : transitionRepository.findByMachineId(machineId == null ? "" : machineId)) {
Collection<Action<String, String>> actions = new ArrayList<Action<String, String>>();
Set<? extends RepositoryAction> repositoryActions = t.getActions();
if (repositoryActions != null) {
for (RepositoryAction repositoryAction : repositoryActions) {
Action<String, String> action = null;
if (StringUtils.hasText(repositoryAction.getName())) {
action = resolveAction(repositoryAction.getName());
} else if (StringUtils.hasText(repositoryAction.getSpel())) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
action = new SpelExpressionAction<String, String>(parser.parseExpression(repositoryAction.getSpel()));
}
if (action != null) {
actions.add(action);
}
}
}
TransitionKind kind = t.getKind();
Guard<String, String> guard = resolveGuard(t);
transitionData.add(new TransitionData<>(t.getSource().getState(), t.getTarget().getState(), t.getEvent(), actions, guard, kind != null ? kind : TransitionKind.EXTERNAL));
if (t.getSource().getKind() == PseudoStateKind.ENTRY) {
entrys.add(new EntryData<String, String>(t.getSource().getState(), t.getTarget().getState()));
} else if (t.getSource().getKind() == PseudoStateKind.EXIT) {
exits.add(new ExitData<String, String>(t.getSource().getState(), t.getTarget().getState()));
} else if (t.getSource().getKind() == PseudoStateKind.CHOICE) {
LinkedList<ChoiceData<String, String>> list = choices.get(t.getSource().getState());
if (list == null) {
list = new LinkedList<ChoiceData<String, String>>();
choices.put(t.getSource().getState(), list);
}
guard = resolveGuard(t);
// we want null guards to be at the end
if (guard == null) {
list.addLast(new ChoiceData<String, String>(t.getSource().getState(), t.getTarget().getState(), guard));
} else {
list.addFirst(new ChoiceData<String, String>(t.getSource().getState(), t.getTarget().getState(), guard));
}
} else if (t.getSource().getKind() == PseudoStateKind.JUNCTION) {
LinkedList<JunctionData<String, String>> list = junctions.get(t.getSource().getState());
if (list == null) {
list = new LinkedList<JunctionData<String, String>>();
junctions.put(t.getSource().getState(), list);
}
guard = resolveGuard(t);
// we want null guards to be at the end
if (guard == null) {
list.addLast(new JunctionData<String, String>(t.getSource().getState(), t.getTarget().getState(), guard));
} else {
list.addFirst(new JunctionData<String, String>(t.getSource().getState(), t.getTarget().getState(), guard));
}
} else if (t.getSource().getKind() == PseudoStateKind.FORK) {
List<String> list = forks.get(t.getSource().getState());
if (list == null) {
list = new ArrayList<String>();
forks.put(t.getSource().getState(), list);
}
list.add(t.getTarget().getState());
} else if (t.getTarget().getKind() == PseudoStateKind.JOIN) {
List<String> list = joins.get(t.getTarget().getState());
if (list == null) {
list = new ArrayList<String>();
joins.put(t.getTarget().getState(), list);
}
list.add(t.getSource().getState());
} else if (t.getSource().getKind() == PseudoStateKind.HISTORY_SHALLOW) {
historys.add(new HistoryData<String, String>(t.getSource().getState(), t.getTarget().getState()));
} else if (t.getSource().getKind() == PseudoStateKind.HISTORY_DEEP) {
historys.add(new HistoryData<String, String>(t.getSource().getState(), t.getTarget().getState()));
}
}
HashMap<String, List<ChoiceData<String, String>>> choicesCopy = new HashMap<String, List<ChoiceData<String, String>>>();
choicesCopy.putAll(choices);
HashMap<String, List<JunctionData<String, String>>> junctionsCopy = new HashMap<String, List<JunctionData<String, String>>>();
junctionsCopy.putAll(junctions);
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData, choicesCopy, junctionsCopy, forks, joins,
entrys, exits, historys);
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);
return stateMachineModel;
}
private Guard<String, String> resolveGuard(RepositoryTransition t) {
Guard<String, String> guard = null;
RepositoryGuard repositoryGuard = t.getGuard();
if (repositoryGuard != null) {
if (StringUtils.hasText(repositoryGuard.getName())) {
guard = resolveGuard(repositoryGuard.getName());
} else if (StringUtils.hasText(repositoryGuard.getSpel())) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
guard = new SpelExpressionGuard<>(parser.parseExpression(repositoryGuard.getSpel()));
}
}
return guard;
}
}