StateMachineInterceptor fired on transient states - spring

I have a transition from A -> X_choice, and X_choice will go to either B or C. I would like to have an interceptor so i can log transitions from A -> B || C. When i try to hook into any of the possible events, (preStateChanged, etc), I only get the transition going from A -> X_choice. The callbacks don't get fired when going from X_choice -> B or X_choice. Is there any way for me to easily do this?
#Override
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
states.withStates()
.initial(States.A)
.junction(States.X_choice)
.state(States.B)
.state(States.C)
#Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transition
.withExternal().source(States.A).target(States.X_choice).event(Events.E).and()
.withJunction()
.source(States.X_choice)
.first(States.B, guard())
.last(States.C).and()
And I have a StateMachinerInteceptor defined as follows:
private class PersistingStateChangeInterceptor extends StateMachineInterceptorAdapter<States, Events> {
#Override
public void preStateChange(State<AppCfgBreatheAgain.States, AppCfgBreatheAgain.Events> state, Message<AppCfgBreatheAgain.Events> message, Transition<AppCfgBreatheAgain.States, AppCfgBreatheAgain.Events> transition, StateMachine<AppCfgBreatheAgain.States, AppCfgBreatheAgain.Events> stateMachine) {
transition.getSource(); //A
transition.getTarget(); //X_Choice
}
What I want is this:
transition.getSource(); //A
transition.getTarget(); //B
Or alternatively two callbacks, one going from A -> X_choice, and one from X_choice -> B. Is this feasible? I need this as I am using the Persist recipe, and the persist is actually persisting the Choice state instead of the actual terminal state.

Developer resolved this issue in this related github ticket:
https://github.com/spring-projects/spring-statemachine/issues/266

Related

Spring state machine configuration for rest service requests

I have a simple order processing application and trying to apply spring state machine for handling states of the order.
And I wondering how can I handle order's states for the same order during multiple request from rest service.
Order states:
enum OrderEvents {
FULFILL,
PAY,
CANCEL
}
Order events:
enum OrderStates {
SUBMITTED,
PAID,
FULFILLED,
CANCELLED
}
State machine configuration:
#Log
#Configuration
#EnableStateMachineFactory
class SimpleEnumStatemachineConfiguration extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {
#Override
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
transitions
.withExternal().source(OrderStates.SUBMITTED).target(OrderStates.PAID).event(OrderEvents.PAY)
.and()
.withExternal().source(OrderStates.PAID).target(OrderStates.FULFILLED).event(OrderEvents.FULFILL)
.and()
.withExternal().source(OrderStates.SUBMITTED).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL)
.and()
.withExternal().source(OrderStates.PAID).target(OrderStates.CANCELLED).event(OrderEvents.CANCEL);
}
#Override
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
states
.withStates()
.initial(OrderStates.SUBMITTED)
.state(OrderStates.PAID)
.end(OrderStates.FULFILLED)
.end(OrderStates.CANCELLED);
}
#Override
public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
config.withConfiguration()
.autoStartup(true)
}
}
In my order service I call
StateMachine<OrderStates, OrderEvents> sm = this.factory.getStateMachine(orderIdKey);
But it seems that every time is new state machine created even for the same orderIdKey. So, how can I get access to state machine created when order was submitted on next state?
You have basically two options:
a) persist the state machine for the given orderId, using a state machine persister as explained here.
b) create a new state machine for given orderId (per HTTP request) and rehydrate the SM state based on the state of the order entity for the given orderId. SM objects are considered lightweight, so this is a viable approach as well. Below is a code sample:
StateMachine<Status, Event> build(long orderId) {
orderService.getOrder(orderId) //returns Optional
.map(order -> {
StateMachine<Status, Event> sm = stateMachineFactory.getStateMachine(Long.toString(orderId));
sm.stop();
rehydrateState(sm, sm.getExtendedState, order.getStatus());
sm.start();
return sm;
})
.orElseGet(() -> createNewStateMachine(orderId);
}
void rehydrateState(StateMachine<Status, Event> newStateMachine, ExtendedState extendedState, Status orderStatus) {
newStateMachine.getStateMachineAccessor().doWithAllRegions(sma ->
sma.resetStateMachine(new DefaultStateMachineContext<>(orderStatus, null, null, extendedState));
});
}

Spring state-machine: common transition

Is there a way to configure common external transition for multiple events?
i.e.
For all states X,Y,Z -> CANCELED on CANCEL event.
Where X,Y,Z - sources, CANCELED - target state
For CANCELED state run common internal transition on all events
I hame more than 20 states to apply this transition to and it is not convenient to manually configure all of them.
I am using EnumStateMachineConfigurerAdapter and #EnableStateMachineFactory to configure state machine, my configuration looks like this
#Configuration
#EnableStateMachineFactory
public class ContentOnlyStateMachineConfig extends EnumStateMachineConfigurerAdapter<TaskState, WorkflowEvent> {
private final Action<TaskState, WorkflowEvent> someAction;
private final Action<TaskState, WorkflowEvent> anotherAction;
#Autowired
public ContentOnlyStateMachineConfig(
final Action<TaskState, WorkflowEvent> someAction,
final Action<TaskState, WorkflowEvent> anotherAction) {
this.someAction = someAction;
this.anotherAction = anotherAction;
}
#Override
public void configure(final StateMachineStateConfigurer<TaskState, WorkflowEvent> states)
throws Exception {
states
.withStates()
.initial(TaskState.CONTENT)
.end(TaskState.COMPLETE)
.state(TaskState.CONTENT, someAction)
.state(TaskState.COMPLETE, anotherAction);
}
#Override
public void configure(
final StateMachineTransitionConfigurer<TaskState, WorkflowEvent> transitions)
throws Exception {
transitions
.withExternal()
.source(TaskState.CONTENT).target(TaskState.SOME_STATE)
.event(WorkflowEvent.EXCEPTION)
.and()
.withExternal()
.source(TaskState.EXCEPTION).target(TaskState.CANCELED)
.event(WorkflowEvent.CANCEL)
.and()
.withExternal()
.source(TaskState.SOME_STATE_2).target(TaskState.SOME_STATE_1)
.event(WorkflowEvent.SOME_EVENT_3)
.and()
.withExternal()
.source(TaskState.SOME_STATE).target(TaskState.SOME_STATE_2)
.event(WorkflowEvent.SOME_EVENT_2)
.and()
.withExternal()
.source(TaskState.SOME_STATE).target(TaskState.COMPLETE)
.event(WorkflowEvent.ADMIN_ACCEPT)
.and()
.withExternal()
.source(TaskState.SOME_STATE).target(TaskState.CONTENT)
.event(WorkflowEvent.SOME_EVENT);
}
}
There's not shortcuts to allow user to create a spaghetti ;). Having said that, if your statechart after you have drawn it in a paper looks like spaghetti rather that clear statechart, I'd argue you're doing something wrong.
Keeping everything in a one flat machine easily creates this kind of state explosion whey configuration itself is starting to look difficult to understand. Usually this is a moment when machine designer needs to start thinking about use of nested substates. If you have multiple states where same event should take machine to same state, you already have a shared functionality in those states.
Putting those states into substates would then allow one single transition from their parent state to transit away. This is usually a model how things are done in machines.
It's not supported out-of-the-box a way to configure a transition with multiple sources and a single target.
Need to do this explicitly.
For example:
#Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions.withExternal()
// normal transitions configuration ..
// common error transitions
for (var state : EnumSet.of(States.A, States,B, States.C)) {
transitions.withExternal().source(state).target(States.ERROR).event(ERROR_EVENT);
}
}
(Can't use Stream.of() since withExternal() throws a checked exception..)

Spring Batch Processor Exception Listener?

I have a partitioned Spring Batch job that reads several split up CSV files and processes each in their own thread, then writes the results to a corresponding output file.
If an item fails to process though (an exception is thrown), I want to write that result to an error file. Is there a way to add a writer or listener that can handle this?
Taking this one step further, is there a way to split this up by exception type and write the different exceptions to different files?
You can achieve this by specifying SkipPolicy. Implement this interface and add your own logic.
public class MySkipper implements SkipPolicy {
#Override
public boolean shouldSkip(Throwable exception, int skipCount) throws SkipLimitExceededException {
if (exception instanceof XYZException) {
//doSomething
}
......
}
You can specify this skip policy in your batch.
this.stepBuilders.get("importStep").<X, Y>chunk(10)
.reader(this.getItemReader()).faultTolerant().skipPolicy(....)
.processor(this.getItemProcessor())
.writer(this.getItemWriter())
.build();
One way that I have seen this done is through a combination of a SkipPolicy and a SkipListener.
The policy would allow you to skip over items that threw an exception, such as a FlatFileParseException (skippable exceptions can be configured).
The listener gives you access to the Throwable and the item that caused it (or just Throwable in the case of reads). The skip listener also lets you differentiate between skips in the read/processor/writer if you wanted to handle those separately.
public class ErrorWritingSkipListener<T, S> implements SkipListener<T, S> {
#Override
public void onSkipInRead(final Throwable t) {
// custom logic
}
#Override
public void onSkipInProcess(final T itemThatFailed, final Throwable t) {
// custom logic
}
#Override
public void onSkipInWrite(final S itemThatFailed, final Throwable t) {
// custom logic
}
}
I would recommend using the SkipPolicy only to identify the exceptions you want to write out to your various files, and leveraging the SkipListener to perform the actual file writing logic. That would match up nicely with their intended use as defined by their interfaces.

spring state machine changing to one of two possible states based on action

i'm having trouble configuring my SSM for a very simple scenario:
the culprit is the 'validation' action that needs to happen when the log_on event is fired. i re-read the SSM documentation and am not sure if this is a 'fork', 'guard' or a hierarchical configuration.
the 'action' is performed here:
#Component
public class LogonValidationAction implements Action<PickStates, PickEvents> {
Logger logger = LogManager.getLogger();
volatile int counter = 0;
#Override
public void execute(StateContext<PickStates, PickEvents> context) {
String eventName = context.getEvent().name();
logger.info("executing {} for event {}", LogonValidationAction.class.getName(), eventName);
// basically, if success, send success, if failure, send failure
// for testing
if(counter % 2 == 0)
context.getStateMachine().sendEvent(PickEvents.logon_sucess);
else
context.getStateMachine().sendEvent(PickEvents.logon_fail);
}
}
i am using 'counter' in my test to direct the flow to either 'failure' (logged_off state remains) or 'success' (state changes to logged_on).
here's the configuration:
#Configuration
#EnableStateMachineFactory
public class Config extends EnumStateMachineConfigurerAdapter<PickStates, PickEvents>{
#Autowired
StateMachineListener stateMachineListener;
#Autowired
LogonValidationAction logonValidationAction;
#Override
public void configure(StateMachineStateConfigurer<PickStates, PickEvents> states) throws Exception {
states
.withStates()
.initial(PickStates.logged_off)
.state(PickStates.logged_on)
.states(EnumSet.allOf(PickStates.class));
}
#Override
public void configure(StateMachineTransitionConfigurer<PickStates, PickEvents> transitions) throws Exception {
transitions
.withExternal()
.source(PickStates.logged_off)
.target(PickStates.logged_on)
.event(PickEvents.log_on)
.action(logonValidationAction)
.and()
.withExternal()
.source(PickStates.logged_on)
.target(PickStates.logged_off)
.event(PickEvents.log_off);
}
#Override
public void configure(StateMachineConfigurationConfigurer<PickStates, PickEvents> config) throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(stateMachineListener);
}
}
this is the failing test (expecting 'logged_off' when the action fires the logon_fail event):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
public class EzpickStateMachineTest {
Logger logger = LogManager.getLogger();
#Autowired
StateMachineFactory<PickStates, PickEvents> stateMachineFactory;
// for testing so can toggle success/failure when logging in
#Autowired
LogonValidationAction logonValidationAction;
#Test
public void failLogon() throws Exception {
StateMachine<PickStates, PickEvents> stateMachine = stateMachineFactory.getStateMachine();
stateMachine.start();
// when sm starts, state is 'logged_off'
assertThat(stateMachine.getState().getId().name(), is(PickStates.logged_off.name()));
// odd numbers fire 'failure' event
logonValidationAction.setCounter(1);
stateMachine.sendEvent(PickEvents.log_on);
// if logon fails, state is 'logged_off'
assertThat(stateMachine.getState().getId().name(), is(PickStates.logged_off.name()));
}
}
From action you try to send either logon_sucess or logon_fail but in machine configuration you haven't defined any transitions for those events so machine would not do anything.
Other notes, don't store counter in action bean, use machine extended state variables for it as then it becomes visible to every component using machine or StateContext.
When that counter is available in extended state, you can i.e. create an anonymous transition with a guard which uses that counter from extended state. One other possibility is to use choice pseudostate and guards to make a choice between target states. Your use of counter is exactly what wiki mention about in its extended state section https://en.wikipedia.org/wiki/UML_state_machine#Extended_states

Using tick tuples with trident in storm

I am able to use standard spout,bolt combination to do streaming aggregation
and works very well in happy case, when using tick tuples to persist data at some interval
to make use of batching. Right now i am doing some failure management (tracking off tuples not saved etc) myself.(i.e not ootb from storm)
But i have read that trident gives you a higher abstraction and better failure management.
What i dont understand is whether there is tick tuple support in trident. Basically
I would like to batch in memory for the current minute or so and persist any aggregated data
for the previous minutes using trident.
Any pointers here or design suggestions would be helpful.
Thanks
Actually micro-batching is a built-in Trident's feature. You don't need any tick tuples for that. When you have something like this in your code:
topology
.newStream("myStream", spout)
.partitionPersist(
ElasticSearchEventState.getFactoryFor(connectionProvider),
new Fields("field1", "field2"),
new ElasticSearchEventUpdater()
)
(I'm using here my custom ElasticSearch state/updater, you might use something else)
So when you have something like this, under the hood Trident group your stream into batches and performs partitionPersist operation not on individual tuples but on those batches.
If you still need tick tuples for any reason, just create your tick spout, something like this works for me:
public class TickSpout implements IBatchSpout {
public static final String TIMESTAMP_FIELD = "timestamp";
private final long delay;
public TickSpout(long delay) {
this.delay = delay;
}
#Override
public void open(Map conf, TopologyContext context) {
}
#Override
public void emitBatch(long batchId, TridentCollector collector) {
Utils.sleep(delay);
collector.emit(new Values(System.currentTimeMillis()));
}
#Override
public void ack(long batchId) {
}
#Override
public void close() {
}
#Override
public Map getComponentConfiguration() {
return null;
}
#Override
public Fields getOutputFields() {
return new Fields(TIMESTAMP_FIELD);
}
}

Resources