I have created a StateMachinePersist
implementation to persist the StateMachineContext y HttpSession:
@Suppress("UNCHECKED_CAST")
class SessionStateMachinePersist(private val session: HttpSession) : StateMachinePersist<States, String, String> {
private val logger = LoggerFactory.getLogger(javaClass)
override fun read(contextObj: String): StateMachineContext<States, String>? {
val stateMachineContext = session.getAttribute("${session.id}-$contextObj") as StateMachineContext<States, String>?
logger.debug("Session {} has state machine context {}?", session.id, contextObj, stateMachineContext != null)
return stateMachineContext
}
override fun write(context: StateMachineContext<States, String>, contextObj: String) {
session.setAttribute("${session.id}-$contextObj", context)
}
}
When it is going to persist the DefaultStateMachineContext
it throws the following exception since the DefaultStateMachineContext
isn't serializable: Caused by: java.io.NotSerializableException: org.springframework.statemachine.support.DefaultStateMachineContext
How can I use a StateMachineContext implementation that is serializable?
This is how I managed to make the State Machine Context serializable and persist it in the Http Session:
SerializableStateMachineContext
class SerializableStateMachineContext<S, E>(var id: String?, var childs: List<StateMachineContext<S, E>>?, var state: S?,
var historyStates: Map<S, S>?, var event: E?, var eventHeaders: Map<String, Any>?,
var extendedState: SerializableExtendedState?) : Serializable {
constructor() : this(null, null, null, null, null, null, null)
}
SerializableExtendedState
class SerializableExtendedState(var variables: Map<Any, Any>) : Serializable {
constructor() : this(mutableMapOf())
}
SessionStateMachinePersist
@Suppress("UNCHECKED_CAST")
class SessionStateMachinePersist(private val session: HttpSession) : StateMachinePersist<States, String, String> {
private val logger = LoggerFactory.getLogger(javaClass)
override fun read(contextObj: String): StateMachineContext<States, String>? {
val stateMachineContext = session.getAttribute("${session.id}-$contextObj") as SerializableStateMachineContext<States, String>?
logger.debug("Session {} has state machine context {}?", session.id, contextObj, stateMachineContext != null)
return DefaultStateMachineContext(stateMachineContext?.childs ?: mutableListOf(), stateMachineContext?.state, stateMachineContext?.event,
stateMachineContext?.eventHeaders, DefaultExtendedState(stateMachineContext?.extendedState?.variables ?: mutableMapOf()), stateMachineContext?.historyStates)
}
override fun write(context: StateMachineContext<States, String>, contextObj: String) {
val serializableExtendedState = SerializableExtendedState(context.extendedState.variables.toMap())
val serializableStateMachineContext = SerializableStateMachineContext(null, context.childs, context.state, context.historyStates, context.event, context.eventHeaders, serializableExtendedState)
session.setAttribute("${session.id}-$contextObj", serializableStateMachineContext)
}
}
Then, wherever you have to operate with the state machine you just have to restore it, execute the trigger operation and persist it, e.g.:
@PostMapping(value = ["/process-input"], consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
fun processInput(session: HttpSession, @RequestBody projectCreationInput: ProjectCreationInput, model: ModelMap, status: SessionStatus) {
val stateMachinePersister = buildStateMachinePersister(session)
stateMachinePersister.restore(stateMachine, "myid")
...
triggerStateMachineExecution(projectCreationInput, event, status)
...
stateMachinePersister.persist(stateMachine, "myid")
}
private fun buildStateMachinePersister(session: HttpSession): DefaultStateMachinePersister<States, String, String> {
val stateMachinePersist = SessionStateMachinePersist(session)
return DefaultStateMachinePersister(stateMachinePersist)
}