Skip to content

Commit 27d67bf

Browse files
committed
Fix StateMachineHandler method caching
- Adding ApplicationListener so that we can consult time of last ContextRefreshedEvent and rebuild a cache of StateMachineHandler methods. - This implementation is a bit naive as we speak but work for #224 will need to add better concept to work outside of app context. - Fixes #232
1 parent 07b3b8d commit 27d67bf

File tree

4 files changed

+136
-11
lines changed

4 files changed

+136
-11
lines changed

spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineCommonConfiguration.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,8 @@ public TaskScheduler taskScheduler() {
4242
return new ConcurrentTaskScheduler();
4343
}
4444

45+
@Bean(name = StateMachineHandlerApplicationListener.BEAN_NAME)
46+
public StateMachineHandlerApplicationListener stateMachineHandlerApplicationListener() {
47+
return new StateMachineHandlerApplicationListener();
48+
}
4549
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.statemachine.config.configuration;
17+
18+
import org.springframework.context.ApplicationListener;
19+
import org.springframework.context.event.ContextRefreshedEvent;
20+
21+
/**
22+
* Spring {@link ApplicationListener} which hooks to {@code ContextRefreshedEvent}
23+
* and tracks when was a last time context was refreshed.
24+
*
25+
* @author Janne Valkealahti
26+
*
27+
*/
28+
public class StateMachineHandlerApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
29+
30+
public final static String BEAN_NAME = "stateMachineHandlerApplicationListener";
31+
private Long lastRefreshTime = null;
32+
33+
@Override
34+
public void onApplicationEvent(ContextRefreshedEvent event) {
35+
lastRefreshTime = System.currentTimeMillis();
36+
}
37+
38+
/**
39+
* Gets the last refresh time.
40+
*
41+
* @return the last refresh time or {@code NULL} if not yet refreshed.
42+
*/
43+
public Long getLastRefreshTime() {
44+
return lastRefreshTime;
45+
}
46+
}

spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.statemachine.annotation.OnTransitionEnd;
4545
import org.springframework.statemachine.annotation.OnTransitionStart;
4646
import org.springframework.statemachine.annotation.WithStateMachine;
47+
import org.springframework.statemachine.config.configuration.StateMachineHandlerApplicationListener;
4748
import org.springframework.statemachine.state.State;
4849
import org.springframework.statemachine.support.StateMachineUtils;
4950
import org.springframework.util.Assert;
@@ -64,6 +65,8 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
6465
private final Log log = LogFactory.getLog(StateMachineHandlerCallHelper.class);
6566
private final Map<String, List<CacheEntry>> cache = new HashMap<>();
6667
private ListableBeanFactory beanFactory;
68+
private StateMachineHandlerApplicationListener stateMachineHandlerApplicationListener;
69+
private long last = Long.MIN_VALUE;
6770

6871
@SuppressWarnings("unchecked")
6972
@Override
@@ -72,6 +75,10 @@ public void afterPropertiesSet() throws Exception {
7275
log.info("Beanfactory is not instance of ListableBeanFactory, was " + beanFactory + " thus Disabling handlers.");
7376
return;
7477
}
78+
if (beanFactory.containsBean(StateMachineHandlerApplicationListener.BEAN_NAME)) {
79+
this.stateMachineHandlerApplicationListener = beanFactory.getBean(StateMachineHandlerApplicationListener.BEAN_NAME,
80+
StateMachineHandlerApplicationListener.class);
81+
}
7582
for (StateMachineHandler<? extends Annotation, S, E> handler : beanFactory.getBeansOfType(StateMachineHandler.class).values()) {
7683
Annotation annotation = handler.getAnnotation();
7784
Annotation metaAnnotation = handler.getMetaAnnotation();
@@ -98,7 +105,7 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
98105
public void callOnStateChanged(String stateMachineId, StateContext<S, E> stateContext) {
99106
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
100107
String cacheKey = OnStateChanged.class.getName() + stateMachineId;
101-
List<CacheEntry> list = cache.get(cacheKey);
108+
List<CacheEntry> list = getCacheEntries(cacheKey);
102109
if (list == null) {
103110
return;
104111
}
@@ -115,7 +122,7 @@ public void callOnStateChanged(String stateMachineId, StateContext<S, E> stateCo
115122
public void callOnStateEntry(String stateMachineId, StateContext<S, E> stateContext) {
116123
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
117124
String cacheKey = OnStateEntry.class.getName() + stateMachineId;
118-
List<CacheEntry> list = cache.get(cacheKey);
125+
List<CacheEntry> list = getCacheEntries(cacheKey);
119126
if (list == null) {
120127
return;
121128
}
@@ -132,7 +139,7 @@ public void callOnStateEntry(String stateMachineId, StateContext<S, E> stateCont
132139
public void callOnStateExit(String stateMachineId, StateContext<S, E> stateContext) {
133140
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
134141
String cacheKey = OnStateExit.class.getName() + stateMachineId;
135-
List<CacheEntry> list = cache.get(cacheKey);
142+
List<CacheEntry> list = getCacheEntries(cacheKey);
136143
if (list == null) {
137144
return;
138145
}
@@ -149,7 +156,7 @@ public void callOnStateExit(String stateMachineId, StateContext<S, E> stateConte
149156
public void callOnEventNotAccepted(String stateMachineId, StateContext<S, E> stateContext) {
150157
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
151158
String cacheKey = OnEventNotAccepted.class.getName() + stateMachineId;
152-
List<CacheEntry> list = cache.get(cacheKey);
159+
List<CacheEntry> list = getCacheEntries(cacheKey);
153160
if (list == null) {
154161
return;
155162
}
@@ -170,7 +177,7 @@ public void callOnEventNotAccepted(String stateMachineId, StateContext<S, E> sta
170177
public void callOnTransitionStart(String stateMachineId, StateContext<S, E> stateContext) {
171178
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
172179
String cacheKey = OnTransitionStart.class.getName() + stateMachineId;
173-
List<CacheEntry> list = cache.get(cacheKey);
180+
List<CacheEntry> list = getCacheEntries(cacheKey);
174181
if (list == null) {
175182
return;
176183
}
@@ -187,7 +194,7 @@ public void callOnTransitionStart(String stateMachineId, StateContext<S, E> stat
187194
public void callOnTransition(String stateMachineId, StateContext<S, E> stateContext) {
188195
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
189196
String cacheKey = OnTransition.class.getName() + stateMachineId;
190-
List<CacheEntry> list = cache.get(cacheKey);
197+
List<CacheEntry> list = getCacheEntries(cacheKey);
191198
if (list == null) {
192199
return;
193200
}
@@ -204,7 +211,7 @@ public void callOnTransition(String stateMachineId, StateContext<S, E> stateCont
204211
public void callOnTransitionEnd(String stateMachineId, StateContext<S, E> stateContext) {
205212
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
206213
String cacheKey = OnTransitionEnd.class.getName() + stateMachineId;
207-
List<CacheEntry> list = cache.get(cacheKey);
214+
List<CacheEntry> list = getCacheEntries(cacheKey);
208215
if (list == null) {
209216
return;
210217
}
@@ -221,7 +228,7 @@ public void callOnTransitionEnd(String stateMachineId, StateContext<S, E> stateC
221228
public void callOnStateMachineStart(String stateMachineId, StateContext<S, E> stateContext) {
222229
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
223230
String cacheKey = OnStateMachineStart.class.getName() + stateMachineId;
224-
List<CacheEntry> list = cache.get(cacheKey);
231+
List<CacheEntry> list = getCacheEntries(cacheKey);
225232
if (list == null) {
226233
return;
227234
}
@@ -234,7 +241,7 @@ public void callOnStateMachineStart(String stateMachineId, StateContext<S, E> st
234241
public void callOnStateMachineStop(String stateMachineId, StateContext<S, E> stateContext) {
235242
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
236243
String cacheKey = OnStateMachineStop.class.getName() + stateMachineId;
237-
List<CacheEntry> list = cache.get(cacheKey);
244+
List<CacheEntry> list = getCacheEntries(cacheKey);
238245
if (list == null) {
239246
return;
240247
}
@@ -247,7 +254,7 @@ public void callOnStateMachineStop(String stateMachineId, StateContext<S, E> sta
247254
public void callOnStateMachineError(String stateMachineId, StateContext<S, E> stateContext) {
248255
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
249256
String cacheKey = OnStateMachineError.class.getName() + stateMachineId;
250-
List<CacheEntry> list = cache.get(cacheKey);
257+
List<CacheEntry> list = getCacheEntries(cacheKey);
251258
if (list == null) {
252259
return;
253260
}
@@ -260,7 +267,7 @@ public void callOnStateMachineError(String stateMachineId, StateContext<S, E> st
260267
public void callOnExtendedStateChanged(String stateMachineId, Object key, Object value, StateContext<S, E> stateContext) {
261268
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
262269
String cacheKey = OnExtendedStateChanged.class.getName() + stateMachineId;
263-
List<CacheEntry> list = cache.get(cacheKey);
270+
List<CacheEntry> list = getCacheEntries(cacheKey);
264271
if (list == null) {
265272
return;
266273
}
@@ -272,6 +279,24 @@ public void callOnExtendedStateChanged(String stateMachineId, Object key, Object
272279
getStateMachineHandlerResults(handlersList, stateContext);
273280
}
274281

282+
private synchronized List<CacheEntry> getCacheEntries(String cacheKey) {
283+
if (stateMachineHandlerApplicationListener != null) {
284+
Long l = stateMachineHandlerApplicationListener.getLastRefreshTime();
285+
if (l != null && l < System.currentTimeMillis() ) {
286+
if (last != l) {
287+
cache.clear();
288+
try {
289+
afterPropertiesSet();
290+
} catch (Exception e) {
291+
log.error("Unable to update handler cache", e);
292+
}
293+
last = l;
294+
}
295+
}
296+
}
297+
return cache.get(cacheKey);
298+
}
299+
275300
private boolean annotationHandlerVariableMatch(Annotation annotation, Object key) {
276301
boolean handle = false;
277302
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation);

spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.statemachine.annotation.WithStateMachine;
3939
import org.springframework.statemachine.config.EnableStateMachine;
4040
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
41+
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
4142
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
4243
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
4344

@@ -175,6 +176,24 @@ public void testMethodsThrowDoesNotBreakMachine() throws Exception {
175176
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S3));
176177
}
177178

179+
@Test
180+
@SuppressWarnings("unchecked")
181+
public void testBeansCreatedAfterMachine() throws Exception {
182+
// autostart is causing lifecycle before beans from BeanConfig1
183+
// are created.
184+
context.register(Config7.class, BeanConfig1.class);
185+
context.refresh();
186+
ObjectStateMachine<TestStates,TestEvents> machine =
187+
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
188+
Bean1 bean1 = context.getBean(Bean1.class);
189+
machine.start();
190+
// S1 is transitioned during lifecycle start which happens
191+
// before all beans are started, so onMethod0Latch is not called
192+
assertThat(bean1.onMethod0Latch.await(2, TimeUnit.SECONDS), is(false));
193+
machine.sendEvent(TestEvents.E1);
194+
assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true));
195+
}
196+
178197
@WithStateMachine
179198
static class Bean1 {
180199

@@ -584,4 +603,35 @@ public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> t
584603

585604
}
586605

606+
@Configuration
607+
@EnableStateMachine
608+
static class Config7 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
609+
610+
@Override
611+
public void configure(
612+
StateMachineConfigurationConfigurer<TestStates, TestEvents> config)
613+
throws Exception {
614+
config
615+
.withConfiguration()
616+
.autoStartup(true);
617+
}
618+
619+
@Override
620+
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
621+
states
622+
.withStates()
623+
.initial(TestStates.S1)
624+
.states(EnumSet.allOf(TestStates.class));
625+
}
626+
627+
@Override
628+
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
629+
transitions
630+
.withExternal()
631+
.source(TestStates.S1)
632+
.target(TestStates.S2)
633+
.event(TestEvents.E1);
634+
}
635+
636+
}
587637
}

0 commit comments

Comments
 (0)