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
This commit is contained in:
Janne Valkealahti
2016-09-30 17:16:53 +01:00
parent 07b3b8ddf7
commit 27d67bfd47
4 changed files with 136 additions and 11 deletions

View File

@@ -42,4 +42,8 @@ public class StateMachineCommonConfiguration {
return new ConcurrentTaskScheduler();
}
@Bean(name = StateMachineHandlerApplicationListener.BEAN_NAME)
public StateMachineHandlerApplicationListener stateMachineHandlerApplicationListener() {
return new StateMachineHandlerApplicationListener();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 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 org.springframework.statemachine.config.configuration;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
/**
* Spring {@link ApplicationListener} which hooks to {@code ContextRefreshedEvent}
* and tracks when was a last time context was refreshed.
*
* @author Janne Valkealahti
*
*/
public class StateMachineHandlerApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
public final static String BEAN_NAME = "stateMachineHandlerApplicationListener";
private Long lastRefreshTime = null;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
lastRefreshTime = System.currentTimeMillis();
}
/**
* Gets the last refresh time.
*
* @return the last refresh time or {@code NULL} if not yet refreshed.
*/
public Long getLastRefreshTime() {
return lastRefreshTime;
}
}

View File

@@ -44,6 +44,7 @@ import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.OnTransitionEnd;
import org.springframework.statemachine.annotation.OnTransitionStart;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.statemachine.config.configuration.StateMachineHandlerApplicationListener;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.support.StateMachineUtils;
import org.springframework.util.Assert;
@@ -64,6 +65,8 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
private final Log log = LogFactory.getLog(StateMachineHandlerCallHelper.class);
private final Map<String, List<CacheEntry>> cache = new HashMap<>();
private ListableBeanFactory beanFactory;
private StateMachineHandlerApplicationListener stateMachineHandlerApplicationListener;
private long last = Long.MIN_VALUE;
@SuppressWarnings("unchecked")
@Override
@@ -72,6 +75,10 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
log.info("Beanfactory is not instance of ListableBeanFactory, was " + beanFactory + " thus Disabling handlers.");
return;
}
if (beanFactory.containsBean(StateMachineHandlerApplicationListener.BEAN_NAME)) {
this.stateMachineHandlerApplicationListener = beanFactory.getBean(StateMachineHandlerApplicationListener.BEAN_NAME,
StateMachineHandlerApplicationListener.class);
}
for (StateMachineHandler<? extends Annotation, S, E> handler : beanFactory.getBeansOfType(StateMachineHandler.class).values()) {
Annotation annotation = handler.getAnnotation();
Annotation metaAnnotation = handler.getMetaAnnotation();
@@ -98,7 +105,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnStateChanged(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnStateChanged.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -115,7 +122,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnStateEntry(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnStateEntry.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -132,7 +139,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnStateExit(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnStateExit.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -149,7 +156,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnEventNotAccepted(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnEventNotAccepted.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -170,7 +177,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnTransitionStart(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnTransitionStart.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -187,7 +194,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnTransition(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnTransition.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -204,7 +211,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnTransitionEnd(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnTransitionEnd.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -221,7 +228,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnStateMachineStart(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnStateMachineStart.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -234,7 +241,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnStateMachineStop(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnStateMachineStop.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -247,7 +254,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnStateMachineError(String stateMachineId, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnStateMachineError.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -260,7 +267,7 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
public void callOnExtendedStateChanged(String stateMachineId, Object key, Object value, StateContext<S, E> stateContext) {
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
String cacheKey = OnExtendedStateChanged.class.getName() + stateMachineId;
List<CacheEntry> list = cache.get(cacheKey);
List<CacheEntry> list = getCacheEntries(cacheKey);
if (list == null) {
return;
}
@@ -272,6 +279,24 @@ public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, Be
getStateMachineHandlerResults(handlersList, stateContext);
}
private synchronized List<CacheEntry> getCacheEntries(String cacheKey) {
if (stateMachineHandlerApplicationListener != null) {
Long l = stateMachineHandlerApplicationListener.getLastRefreshTime();
if (l != null && l < System.currentTimeMillis() ) {
if (last != l) {
cache.clear();
try {
afterPropertiesSet();
} catch (Exception e) {
log.error("Unable to update handler cache", e);
}
last = l;
}
}
}
return cache.get(cacheKey);
}
private boolean annotationHandlerVariableMatch(Annotation annotation, Object key) {
boolean handle = false;
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation);

View File

@@ -38,6 +38,7 @@ import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
@@ -175,6 +176,24 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S3));
}
@Test
@SuppressWarnings("unchecked")
public void testBeansCreatedAfterMachine() throws Exception {
// autostart is causing lifecycle before beans from BeanConfig1
// are created.
context.register(Config7.class, BeanConfig1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
Bean1 bean1 = context.getBean(Bean1.class);
machine.start();
// S1 is transitioned during lifecycle start which happens
// before all beans are started, so onMethod0Latch is not called
assertThat(bean1.onMethod0Latch.await(2, TimeUnit.SECONDS), is(false));
machine.sendEvent(TestEvents.E1);
assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true));
}
@WithStateMachine
static class Bean1 {
@@ -584,4 +603,35 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
}
@Configuration
@EnableStateMachine
static class Config7 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(
StateMachineConfigurationConfigurer<TestStates, TestEvents> config)
throws Exception {
config
.withConfiguration()
.autoStartup(true);
}
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.states(EnumSet.allOf(TestStates.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1);
}
}
}