/*
* Copyright 2015-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 demo.web;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineException;
import org.springframework.statemachine.ensemble.EnsembleListenerAdapter;
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
import org.springframework.statemachine.transition.TransitionKind;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import demo.web.StateMachineConfig.Events;
import demo.web.StateMachineConfig.States;
@Controller
public class StateMachineController {
private final Log log = LogFactory.getLog(StateMachineController.class);
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
@Autowired
private StateMachine<States, Events> stateMachine;
@Autowired
private StateMachineEnsemble<States, Events> stateMachineEnsemble;
@PostConstruct
public void setup() {
stateMachine.addStateListener(new StateMachineListenerAdapter<States, Events>() {
@Override
public void stateEntered(State<States, Events> state) {
StateMachineMessage message = new StateMachineMessage();
message.setMessage("Enter state " + state.getId().toString());
simpMessagingTemplate.convertAndSend("/topic/sm.message", message);
}
@Override
public void stateExited(State<States, Events> state) {
StateMachineMessage message = new StateMachineMessage();
message.setMessage("Exit state " + state.getId().toString());
simpMessagingTemplate.convertAndSend("/topic/sm.message", message);
}
@Override
public void stateChanged(State<States, Events> from, State<States, Events> to) {
Map<Object, Object> variables = stateMachine.getExtendedState().getVariables();
ArrayList<StateMachineEvent> list = new ArrayList<StateMachineEvent>();
for (States state : stateMachine.getState().getIds()) {
list.add(new StateMachineEvent(state.toString()));
}
simpMessagingTemplate.convertAndSend("/topic/sm.states", list);
simpMessagingTemplate.convertAndSend("/topic/sm.variables", variables);
}
@Override
public void transitionEnded(Transition<States, Events> transition) {
if (transition != null && transition.getKind() == TransitionKind.INTERNAL) {
Map<Object, Object> variables = stateMachine.getExtendedState().getVariables();
simpMessagingTemplate.convertAndSend("/topic/sm.variables", variables);
}
}
@Override
public void stateMachineError(StateMachine<States, Events> stateMachine, Exception exception) {
handleStateMachineError(new StateMachineException("Received error from machine", exception));
}
});
stateMachineEnsemble.addEnsembleListener(new EnsembleListenerAdapter<States, Events>() {
@Override
public void ensembleLeaderGranted(StateMachine<States, Events> stateMachine) {
StateMachineMessage message = new StateMachineMessage();
message.setMessage("Leader granted " + stateMachine.getUuid().toString());
simpMessagingTemplate.convertAndSend("/topic/sm.message", message);
}
@Override
public void ensembleLeaderRevoked(StateMachine<States, Events> stateMachine) {
StateMachineMessage message = new StateMachineMessage();
message.setMessage("Leader revoked " + stateMachine.getUuid().toString());
simpMessagingTemplate.convertAndSend("/topic/sm.message", message);
}
});
}
@SubscribeMapping("/sm.uuid")
public String retrieveUuid() {
return stateMachine.getUuid().toString();
}
@SubscribeMapping("/sm.states")
public Collection<StateMachineEvent> retrieveStates() {
ArrayList<StateMachineEvent> list = new ArrayList<StateMachineEvent>();
for (States state : stateMachine.getState().getIds()) {
list.add(new StateMachineEvent(state.toString()));
}
return list;
}
@SubscribeMapping("/sm.variables")
public Map<Object, Object> retrieveVariables() {
return stateMachine.getExtendedState().getVariables();
}
@RequestMapping("/event")
@ResponseStatus(HttpStatus.OK)
public void sendEvent(@RequestParam(value = "id") Events id,
@RequestParam(value = "testVariable", required = false) String testVariable) {
log.info("Got request to send event " + id + " testVariable " + testVariable);
Message<Events> message = MessageBuilder
.withPayload(id)
.setHeader("testVariable", testVariable)
.build();
stateMachine.sendEvent(message);
}
@RequestMapping("/join")
@ResponseStatus(HttpStatus.OK)
public void joinEnsemble() {
stateMachineEnsemble.join(stateMachine);
}
@RequestMapping("/leave")
@ResponseStatus(HttpStatus.OK)
public void leaveEnsemble() {
stateMachineEnsemble.leave(stateMachine);
}
@RequestMapping(value = "/states", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Collection<States> getStates() {
return stateMachine.getState().getIds();
}
@RequestMapping(value = "/status", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Map<Object, Object> getStatus() {
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("hasStateMachineError", stateMachine.hasStateMachineError());
map.put("isComplete", stateMachine.isComplete());
map.put("extendedStateVariables", stateMachine.getExtendedState().getVariables());
return map;
}
@MessageExceptionHandler
@SendToUser(value = "/queue/errors", broadcast = false)
public String handleStateMachineError(StateMachineException e) {
return e.getMessage();
}
}