Skip to content

Commit 06f839f

Browse files
committed
Guard should not break with Throwable
- Now catching all exceptions and throwables with evaluate and return false in those cases. - Fixes #206
1 parent 3d41bef commit 06f839f

File tree

4 files changed

+124
-17
lines changed

4 files changed

+124
-17
lines changed

spring-statemachine-core/src/main/java/org/springframework/statemachine/state/ChoicePseudoState.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import java.util.List;
1919

20+
import org.apache.commons.logging.Log;
21+
import org.apache.commons.logging.LogFactory;
2022
import org.springframework.statemachine.StateContext;
2123
import org.springframework.statemachine.guard.Guard;
2224
import org.springframework.util.Assert;
@@ -31,6 +33,7 @@
3133
*/
3234
public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
3335

36+
private final static Log log = LogFactory.getLog(ChoicePseudoState.class);
3437
private final List<ChoiceStateData<S, E>> choices;
3538

3639
/**
@@ -52,7 +55,7 @@ public State<S, E> entry(StateContext<S, E> context) {
5255
State<S, E> s = null;
5356
for (ChoiceStateData<S, E> c : choices) {
5457
s = c.getState();
55-
if (c.guard != null && c.guard.evaluate(context)) {
58+
if (c.guard != null && evaluateInternal(c.guard, context)) {
5659
break;
5760
}
5861
}
@@ -67,6 +70,15 @@ public void exit(StateContext<S, E> context) {
6770
public void addPseudoStateListener(PseudoStateListener<S, E> listener) {
6871
}
6972

73+
private boolean evaluateInternal(Guard<S, E> guard, StateContext<S, E> context) {
74+
try {
75+
return guard.evaluate(context);
76+
} catch (Throwable t) {
77+
log.warn("Deny guard due to throw as GUARD should not error", t);
78+
return false;
79+
}
80+
}
81+
7082
/**
7183
* Data class wrapping choice {@link State} and {@link Guard}
7284
* together.

spring-statemachine-core/src/main/java/org/springframework/statemachine/state/JunctionPseudoState.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import java.util.List;
1919

20+
import org.apache.commons.logging.Log;
21+
import org.apache.commons.logging.LogFactory;
2022
import org.springframework.statemachine.StateContext;
2123
import org.springframework.statemachine.guard.Guard;
2224
import org.springframework.util.Assert;
@@ -31,6 +33,7 @@
3133
*/
3234
public class JunctionPseudoState<S, E> implements PseudoState<S, E> {
3335

36+
private final static Log log = LogFactory.getLog(JunctionPseudoState.class);
3437
private final List<JunctionStateData<S, E>> junctions;
3538

3639
/**
@@ -52,7 +55,7 @@ public State<S, E> entry(StateContext<S, E> context) {
5255
State<S, E> s = null;
5356
for (JunctionStateData<S, E> j : junctions) {
5457
s = j.getState();
55-
if (j.guard != null && j.guard.evaluate(context)) {
58+
if (j.guard != null && evaluateInternal(j.guard, context)) {
5659
break;
5760
}
5861
}
@@ -67,6 +70,15 @@ public void exit(StateContext<S, E> context) {
6770
public void addPseudoStateListener(PseudoStateListener<S, E> listener) {
6871
}
6972

73+
private boolean evaluateInternal(Guard<S, E> guard, StateContext<S, E> context) {
74+
try {
75+
return guard.evaluate(context);
76+
} catch (Throwable t) {
77+
log.warn("Deny guard due to throw as GUARD should not error", t);
78+
return false;
79+
}
80+
}
81+
7082
/**
7183
* Data class wrapping choice {@link State} and {@link Guard}
7284
* together.

spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import java.util.Collection;
1919

20+
import org.apache.commons.logging.Log;
21+
import org.apache.commons.logging.LogFactory;
2022
import org.springframework.statemachine.StateContext;
2123
import org.springframework.statemachine.action.Action;
2224
import org.springframework.statemachine.guard.Guard;
@@ -35,6 +37,8 @@
3537
*/
3638
public abstract class AbstractTransition<S, E> implements Transition<S, E> {
3739

40+
private final static Log log = LogFactory.getLog(AbstractTransition.class);
41+
3842
private final State<S,E> source;
3943

4044
private final State<S,E> target;
@@ -90,7 +94,12 @@ public Trigger<S, E> getTrigger() {
9094
@Override
9195
public boolean transit(StateContext<S, E> context) {
9296
if (guard != null) {
93-
if (!guard.evaluate(context)) {
97+
try {
98+
if (!guard.evaluate(context)) {
99+
return false;
100+
}
101+
} catch (Throwable t) {
102+
log.warn("Deny guard due to throw as GUARD should not error", t);
94103
return false;
95104
}
96105
}

spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.statemachine.AbstractStateMachineTests.TestGuard;
3434
import org.springframework.statemachine.AbstractStateMachineTests.TestStates;
3535
import org.springframework.statemachine.ObjectStateMachine;
36+
import org.springframework.statemachine.StateContext;
3637
import org.springframework.statemachine.StateMachineSystemConstants;
3738
import org.springframework.statemachine.config.EnableStateMachine;
3839
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
@@ -41,7 +42,7 @@
4142

4243
/**
4344
* Tests for state machine guards.
44-
*
45+
*
4546
* @author Janne Valkealahti
4647
*
4748
*/
@@ -57,12 +58,12 @@ public void testGuardEvaluated() throws Exception {
5758
TestAction testAction = ctx.getBean("testAction", TestAction.class);
5859
assertThat(testGuard, notNullValue());
5960
assertThat(testAction, notNullValue());
60-
61+
6162
machine.start();
62-
machine.sendEvent(TestEvents.E1);
63+
machine.sendEvent(TestEvents.E1);
6364
assertThat(testGuard.onEvaluateLatch.await(2, TimeUnit.SECONDS), is(true));
6465
assertThat(testAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(true));
65-
66+
6667
ctx.close();
6768
}
6869

@@ -76,18 +77,35 @@ public void testGuardDenyAction() throws Exception {
7677
TestAction testAction = ctx.getBean("testAction", TestAction.class);
7778
assertThat(testGuard, notNullValue());
7879
assertThat(testAction, notNullValue());
79-
80+
8081
machine.start();
8182
assertThat(machine.getState().getIds(), contains(TestStates.S1));
82-
83-
machine.sendEvent(TestEvents.E1);
83+
84+
machine.sendEvent(TestEvents.E1);
8485
assertThat(testGuard.onEvaluateLatch.await(2, TimeUnit.SECONDS), is(true));
8586
assertThat(testAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(false));
8687
assertThat(machine.getState().getIds(), contains(TestStates.S1));
87-
88+
8889
ctx.close();
8990
}
90-
91+
92+
@SuppressWarnings({ "unchecked" })
93+
@Test
94+
public void testGuardThrows() throws Exception {
95+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config3.class);
96+
ObjectStateMachine<TestStates,TestEvents> machine =
97+
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
98+
machine.start();
99+
assertThat(machine.getState().getIds(), contains(TestStates.S1));
100+
machine.sendEvent(TestEvents.E1);
101+
assertThat(machine.getState().getIds(), contains(TestStates.S1));
102+
machine.sendEvent(TestEvents.E2);
103+
assertThat(machine.getState().getIds(), contains(TestStates.S1));
104+
machine.sendEvent(TestEvents.E3);
105+
assertThat(machine.getState().getIds(), contains(TestStates.S2));
106+
ctx.close();
107+
}
108+
91109
@Configuration
92110
@EnableStateMachine
93111
public static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@@ -116,12 +134,12 @@ public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> t
116134
public TestAction testAction() {
117135
return new TestAction();
118136
}
119-
137+
120138
@Bean
121139
public TestGuard testGuard() {
122140
return new TestGuard(true);
123141
}
124-
142+
125143
@Bean
126144
public TaskExecutor taskExecutor() {
127145
return new SyncTaskExecutor();
@@ -152,12 +170,12 @@ public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> t
152170
.action(testAction())
153171
.guard(testGuard());
154172
}
155-
173+
156174
@Bean
157175
public TestGuard testGuard() {
158176
return new TestGuard(false);
159177
}
160-
178+
161179
@Bean
162180
public TestAction testAction() {
163181
return new TestAction();
@@ -169,5 +187,61 @@ public TaskExecutor taskExecutor() {
169187
}
170188

171189
}
172-
190+
191+
@Configuration
192+
@EnableStateMachine
193+
public static class Config3 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
194+
195+
@Override
196+
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
197+
states
198+
.withStates()
199+
.initial(TestStates.S1)
200+
.state(TestStates.S1)
201+
.state(TestStates.S2);
202+
}
203+
204+
@Override
205+
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
206+
transitions
207+
.withExternal()
208+
.source(TestStates.S1)
209+
.target(TestStates.S2)
210+
.event(TestEvents.E1)
211+
.guard(testGuard1())
212+
.and()
213+
.withExternal()
214+
.source(TestStates.S1)
215+
.target(TestStates.S2)
216+
.event(TestEvents.E2)
217+
.guard(testGuard2())
218+
.and()
219+
.withExternal()
220+
.source(TestStates.S1)
221+
.target(TestStates.S2)
222+
.event(TestEvents.E3);
223+
}
224+
225+
@Bean
226+
public Guard<TestStates, TestEvents> testGuard1() {
227+
return new Guard<TestStates, TestEvents>() {
228+
229+
@Override
230+
public boolean evaluate(StateContext<TestStates, TestEvents> context) {
231+
throw new RuntimeException();
232+
}
233+
};
234+
}
235+
236+
@Bean
237+
public Guard<TestStates, TestEvents> testGuard2() {
238+
return new Guard<TestStates, TestEvents>() {
239+
240+
@Override
241+
public boolean evaluate(StateContext<TestStates, TestEvents> context) {
242+
throw new Error();
243+
}
244+
};
245+
}
246+
}
173247
}

0 commit comments

Comments
 (0)