Skip to content

Commit 043d2a7

Browse files
gvagenasjvalkeal
authored andcommitted
properly set the bean name for factory generated state machines
- testing: added test case for dynamic generated id - docs: update documentation - fix: Support @WithStateMachine for machines generated using either empty id or dynamically generated id - Fixes #940
1 parent 048e930 commit 043d2a7

File tree

4 files changed

+179
-14
lines changed

4 files changed

+179
-14
lines changed

docs/src/reference/asciidoc/sm-context.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ include::samples/DocsConfigurationSampleTests4.java[tags=snippetAAAA]
4848
----
4949
====
5050

51+
When using StateMachineFactory to generate state machines the state machine using dynamic provided `id`, bean name will default to `stateMachine` it's not possible to use `@WithStateMachine (id = "some-id")` since `id` is only known at runtime.
52+
53+
In such a cases, use either `@WithStateMachine` or `@WithStateMachine(name = "stateMachine")` and all state machines generated by the factory will be atached to your bean or beans.
54+
55+
5156
You can also use `@WithStateMachine` as a meta-annotation, as shown
5257
in the preceding example. In this case, you could annotate your bean with `WithMyBean`.
5358
The following example shows how to do so:

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.statemachine.ObjectStateMachine;
2727
import org.springframework.statemachine.StateContext;
2828
import org.springframework.statemachine.StateMachine;
29+
import org.springframework.statemachine.StateMachineSystemConstants;
2930
import org.springframework.statemachine.config.model.StateMachineModel;
3031
import org.springframework.statemachine.config.model.StateMachineModelFactory;
3132
import org.springframework.statemachine.region.Region;
@@ -86,6 +87,12 @@ protected StateMachine<S, E> buildStateMachineInternal(Collection<State<S, E>> s
8687
machine.setBeanFactory(beanFactory);
8788
}
8889
if (machine instanceof BeanNameAware) {
90+
//When using StateMachineFactory.getStateMachine() to generate state machine,
91+
//which means name and id are null
92+
//in that case set name to the default `stateMachine`
93+
if ((machineId == null || machineId.isEmpty()) && (beanName == null || beanName.isEmpty())) {
94+
beanName = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE;
95+
}
8996
((BeanNameAware)machine).setBeanName(beanName);
9097
}
9198
return machine;

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

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.beans.factory.ListableBeanFactory;
3333
import org.springframework.core.annotation.AnnotationUtils;
3434
import org.springframework.statemachine.StateContext;
35+
import org.springframework.statemachine.StateMachineSystemConstants;
3536
import org.springframework.statemachine.annotation.OnEventNotAccepted;
3637
import org.springframework.statemachine.annotation.OnExtendedStateChanged;
3738
import org.springframework.statemachine.annotation.OnStateChanged;
@@ -92,10 +93,10 @@ public void afterPropertiesSet() throws Exception {
9293
// don't check name if id is set as name defaults to
9394
// 'stateMachine' and would cause additional cache entry
9495
if (StringUtils.hasText(withStateMachine.id())) {
95-
updateCache(metaAnnotation.annotationType().getName() + withStateMachine.id(),
96+
updateCache(metaAnnotation.annotationType().getName() +"_"+ withStateMachine.id(),
9697
new CacheEntry(handler, annotation, metaAnnotation));
9798
} else if (StringUtils.hasText(withStateMachine.name())) {
98-
updateCache(metaAnnotation.annotationType().getName() + withStateMachine.name(),
99+
updateCache(metaAnnotation.annotationType().getName() +"_"+ withStateMachine.name(),
99100
new CacheEntry(handler, annotation, metaAnnotation));
100101
}
101102
}
@@ -118,7 +119,7 @@ public void callOnStateChanged(String stateMachineId, StateContext<S, E> stateCo
118119
return;
119120
}
120121
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
121-
String cacheKey = OnStateChanged.class.getName() + stateMachineId;
122+
String cacheKey = OnStateChanged.class.getName() +"_"+ stateMachineId;
122123
List<CacheEntry> list = getCacheEntries(cacheKey);
123124
if (list == null) {
124125
return;
@@ -138,7 +139,7 @@ public void callOnStateEntry(String stateMachineId, StateContext<S, E> stateCont
138139
return;
139140
}
140141
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
141-
String cacheKey = OnStateEntry.class.getName() + stateMachineId;
142+
String cacheKey = OnStateEntry.class.getName() +"_"+ stateMachineId;
142143
List<CacheEntry> list = getCacheEntries(cacheKey);
143144
if (list == null) {
144145
return;
@@ -158,7 +159,7 @@ public void callOnStateExit(String stateMachineId, StateContext<S, E> stateConte
158159
return;
159160
}
160161
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
161-
String cacheKey = OnStateExit.class.getName() + stateMachineId;
162+
String cacheKey = OnStateExit.class.getName() +"_"+ stateMachineId;
162163
List<CacheEntry> list = getCacheEntries(cacheKey);
163164
if (list == null) {
164165
return;
@@ -178,7 +179,7 @@ public void callOnEventNotAccepted(String stateMachineId, StateContext<S, E> sta
178179
return;
179180
}
180181
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
181-
String cacheKey = OnEventNotAccepted.class.getName() + stateMachineId;
182+
String cacheKey = OnEventNotAccepted.class.getName() +"_"+ stateMachineId;
182183
List<CacheEntry> list = getCacheEntries(cacheKey);
183184
if (list == null) {
184185
return;
@@ -202,7 +203,7 @@ public void callOnTransitionStart(String stateMachineId, StateContext<S, E> stat
202203
return;
203204
}
204205
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
205-
String cacheKey = OnTransitionStart.class.getName() + stateMachineId;
206+
String cacheKey = OnTransitionStart.class.getName() +"_"+ stateMachineId;
206207
List<CacheEntry> list = getCacheEntries(cacheKey);
207208
if (list == null) {
208209
return;
@@ -222,7 +223,7 @@ public void callOnTransition(String stateMachineId, StateContext<S, E> stateCont
222223
return;
223224
}
224225
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
225-
String cacheKey = OnTransition.class.getName() + stateMachineId;
226+
String cacheKey = OnTransition.class.getName() +"_"+ stateMachineId;
226227
List<CacheEntry> list = getCacheEntries(cacheKey);
227228
if (list == null) {
228229
return;
@@ -242,7 +243,7 @@ public void callOnTransitionEnd(String stateMachineId, StateContext<S, E> stateC
242243
return;
243244
}
244245
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
245-
String cacheKey = OnTransitionEnd.class.getName() + stateMachineId;
246+
String cacheKey = OnTransitionEnd.class.getName() +"_"+ stateMachineId;
246247
List<CacheEntry> list = getCacheEntries(cacheKey);
247248
if (list == null) {
248249
return;
@@ -262,7 +263,7 @@ public void callOnStateMachineStart(String stateMachineId, StateContext<S, E> st
262263
return;
263264
}
264265
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
265-
String cacheKey = OnStateMachineStart.class.getName() + stateMachineId;
266+
String cacheKey = OnStateMachineStart.class.getName() +"_"+ stateMachineId;
266267
List<CacheEntry> list = getCacheEntries(cacheKey);
267268
if (list == null) {
268269
return;
@@ -278,7 +279,7 @@ public void callOnStateMachineStop(String stateMachineId, StateContext<S, E> sta
278279
return;
279280
}
280281
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
281-
String cacheKey = OnStateMachineStop.class.getName() + stateMachineId;
282+
String cacheKey = OnStateMachineStop.class.getName() +"_"+ stateMachineId;
282283
List<CacheEntry> list = getCacheEntries(cacheKey);
283284
if (list == null) {
284285
return;
@@ -294,7 +295,7 @@ public void callOnStateMachineError(String stateMachineId, StateContext<S, E> st
294295
return;
295296
}
296297
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
297-
String cacheKey = OnStateMachineError.class.getName() + stateMachineId;
298+
String cacheKey = OnStateMachineError.class.getName() +"_"+ stateMachineId;
298299
List<CacheEntry> list = getCacheEntries(cacheKey);
299300
if (list == null) {
300301
return;
@@ -310,7 +311,7 @@ public void callOnExtendedStateChanged(String stateMachineId, Object key, Object
310311
return;
311312
}
312313
List<StateMachineHandler<? extends Annotation, S, E>> handlersList = new ArrayList<StateMachineHandler<? extends Annotation, S, E>>();
313-
String cacheKey = OnExtendedStateChanged.class.getName() + stateMachineId;
314+
String cacheKey = OnExtendedStateChanged.class.getName() +"_"+ stateMachineId;
314315
List<CacheEntry> list = getCacheEntries(cacheKey);
315316
if (list == null) {
316317
return;
@@ -347,7 +348,15 @@ private synchronized List<CacheEntry> getCacheEntries(String cacheKey) {
347348
}
348349
}
349350
}
350-
return cache.get(cacheKey);
351+
//Try to get the CacheEntry using the provided key
352+
//Or use default machine name in the key
353+
if (cache.containsKey(cacheKey)) {
354+
return cache.get(cacheKey);
355+
} else {
356+
cacheKey = cacheKey.replaceFirst(cacheKey.substring(cacheKey.indexOf("_")+1),
357+
StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE);
358+
return cache.get(cacheKey);
359+
}
351360
}
352361

353362
private boolean annotationHandlerVariableMatch(Annotation annotation, Object key) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2017-2020 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+
* https://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.annotation;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.statemachine.AbstractStateMachineTests;
23+
import org.springframework.statemachine.StateMachine;
24+
import org.springframework.statemachine.StateMachineSystemConstants;
25+
import org.springframework.statemachine.config.EnableStateMachineFactory;
26+
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
27+
import org.springframework.statemachine.config.StateMachineFactory;
28+
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
29+
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
30+
31+
import java.util.EnumSet;
32+
import java.util.concurrent.CountDownLatch;
33+
import java.util.concurrent.TimeUnit;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.springframework.statemachine.TestUtils.doSendEventAndConsumeAll;
37+
import static org.springframework.statemachine.TestUtils.doStartAndAssert;
38+
import static org.springframework.statemachine.TestUtils.resolveFactory;
39+
40+
public class MethodAnnotationWithDefaultsWithFactoryTests extends AbstractStateMachineTests {
41+
42+
@Test
43+
public void testMethodAnnotations() throws Exception {
44+
context.register(BeanConfig1.class, Config1.class);
45+
context.refresh();
46+
47+
Bean1 bean1 = context.getBean(Bean1.class);
48+
Bean2 bean2 = context.getBean(Bean2.class);
49+
50+
StateMachineFactory<TestStates,TestEvents> factory = resolveFactory(context);
51+
StateMachine<TestStates,TestEvents> machine = factory.getStateMachine();
52+
doStartAndAssert(machine);
53+
54+
assertThat(machine.getState().getIds()).containsExactly(TestStates.S1);
55+
doSendEventAndConsumeAll(machine, TestEvents.E1);
56+
assertThat(machine.getState().getIds()).containsExactly(TestStates.S2);
57+
assertThat(bean1.counter).isEqualTo(1);
58+
assertThat(bean2.onStateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue();
59+
}
60+
61+
@Test
62+
public void testMethodAnnotationsWithDynamicId() throws Exception {
63+
context.register(BeanConfig1.class, Config1.class);
64+
context.refresh();
65+
66+
Bean1 bean1 = context.getBean(Bean1.class);
67+
Bean2 bean2 = context.getBean(Bean2.class);
68+
69+
String id = String.valueOf(System.currentTimeMillis());
70+
71+
StateMachineFactory<TestStates,TestEvents> factory = resolveFactory(context);
72+
StateMachine<TestStates,TestEvents> machine = factory.getStateMachine(id);
73+
doStartAndAssert(machine);
74+
75+
assertThat(machine.getState().getIds()).containsExactly(TestStates.S1);
76+
doSendEventAndConsumeAll(machine, TestEvents.E1);
77+
assertThat(machine.getState().getIds()).containsExactly(TestStates.S2);
78+
assertThat(bean1.counter).isEqualTo(1);
79+
assertThat(bean2.onStateChangedLatch.await(1, TimeUnit.SECONDS)).isTrue();
80+
}
81+
82+
@WithStateMachine
83+
static class Bean1 {
84+
85+
int counter = 0;
86+
87+
@OnStateChanged (source = "S1", target = "S2")
88+
public void onStateChanged() {
89+
counter++;
90+
}
91+
}
92+
93+
@WithStateMachine(id = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)
94+
static class Bean2 {
95+
96+
CountDownLatch onStateChangedLatch = new CountDownLatch(1);
97+
98+
@OnStateChanged
99+
public void onStateChanged() {
100+
onStateChangedLatch.countDown();
101+
}
102+
}
103+
104+
@Configuration
105+
static class BeanConfig1 {
106+
107+
@Bean
108+
public Bean1 bean1() {
109+
return new Bean1();
110+
}
111+
112+
@Bean
113+
public Bean2 bean2() {
114+
return new Bean2();
115+
}
116+
}
117+
118+
@Configuration
119+
@EnableStateMachineFactory
120+
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
121+
122+
@Override
123+
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
124+
states
125+
.withStates()
126+
.initial(TestStates.S1)
127+
.states(EnumSet.allOf(TestStates.class));
128+
}
129+
130+
@Override
131+
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
132+
transitions
133+
.withExternal()
134+
.source(TestStates.S1)
135+
.target(TestStates.S2)
136+
.event(TestEvents.E1);
137+
}
138+
}
139+
140+
@Override
141+
protected AnnotationConfigApplicationContext buildContext() {
142+
return new AnnotationConfigApplicationContext();
143+
}
144+
}

0 commit comments

Comments
 (0)