Overhaul annotation and listener processing

- This is state 1 for these changes.
- This is a stage one of fixes for #126 and #138, thus
  not giving full support not complete features.
- Add new annotations in a side of OnTransition
  reflecting stuff from a StateMachineListener.
- Prepare and modify call chains so that we have
  preliminary support(not feature ready) for these
  so that existing tests pass.
- Add new helper to handle some annotation and spel
  call caching.
- Enhance StateContext to better suit these needs.
This commit is contained in:
Janne Valkealahti
2015-12-23 22:20:57 +00:00
parent c4629e1dcd
commit 0ac5785ec5
29 changed files with 1666 additions and 326 deletions

View File

@@ -15,29 +15,44 @@
*/
package org.springframework.statemachine;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
/**
* {@code StateContext} is representing a current context used in
* {@code StateContext} is representing of a current context used in
* various stages in a state machine execution. These include for example
* {@link Transition}s, {@link Action}s and {@link Guard}s order to get access
* to event headers and {@link ExtendedState}.
* <p>
* Context really is not a current state of a state machine but
* more like a snapshot of where state machine is when this context
* is passed to various methods.
*
* @author Janne Valkealahti
*
*/
public interface StateContext<S, E> {
/**
* Gets the message associated with a context. Message may be null if transition
* is not triggered by a signal.
*
* @return the message
*/
Message<E> getMessage();
/**
* Gets the event associated with a context. Event may be null if transition
* is not triggered by a signal.
*
*
* @return the event
*/
E getEvent();
/**
* Gets the event message headers.
*
@@ -75,4 +90,29 @@ public interface StateContext<S, E> {
*/
StateMachine<S, E> getStateMachine();
/**
* Gets the source state of this context. Generally source
* is where a state machine is coming from which may be different
* than what the transition source is.
*
* @return the source state
*/
State<S,E> getSource();
/**
* Gets the tarter state of this context. Generally target
* is where a state machine going to which may be different
* than what the transition target is.
*
* @return the target state
*/
State<S,E> getTarget();
/**
* Gets the exception associated with a context.
*
* @return the exception
*/
Exception getException();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtendedStateVariable {
@AliasFor("key")
String value() default "";
@AliasFor("value")
String key() default "";
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
/**
* Indicates that a method is candidate to be called when event
* is not accepted by a state machine.
* <p>
* A method annotated with @OnEventNotAccepted may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnEventNotAccepted {
/**
* The events.
*
* @return the events
*/
String[] event() default {};
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.statemachine.ExtendedState;
/**
* Indicates that a method is candidate to be called when {@link ExtendedState}
* is changed.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnExtendedStateChanged {
/**
* The extended state variable keys.
*
* @return The extended state variable keys
*/
String[] key() default {};
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.state.State;
/**
* Indicates that a method is candidate to be called when {@link State}
* is changed.
* <p>
* A method annotated with @OnStateChanged may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnStateChanged {
/**
* The source states.
*
* @return the source states.
*/
String[] source() default {};
/**
* The target states.
*
* @return the target states.
*/
String[] target() default {};
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.state.State;
/**
* Indicates that a method is candidate to be called when {@link State}
* is entered.
* <p>
* A method annotated with @OnStateChanged may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnStateEntry {
/**
* The source states.
*
* @return the source states.
*/
String[] source() default {};
/**
* The target states.
*
* @return the target states.
*/
String[] target() default {};
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.state.State;
/**
* Indicates that a method is candidate to be called when {@link State}
* is exited.
* <p>
* A method annotated with @OnStateChanged may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnStateExit {
/**
* The source states.
*
* @return the source states.
*/
String[] source() default {};
/**
* The target states.
*
* @return the target states.
*/
String[] target() default {};
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
/**
* Indicates that a method is candidate to be called when state machine
* has been entered in error it cannot recover.
* <p>
* A method annotated with @OnStateMachineError may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnStateMachineError {
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.statemachine.StateMachine;
/**
* Indicates that a method is a candidate to be called when state machine
* is started.
* <p>
* A method annotated with @OnStateMachineStart may accept a parameter of type
* {@link StateMachine}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnStateMachineStart {
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a method is a candidate to be called when state machine
* is stopped.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnStateMachineStop {
}

View File

@@ -21,15 +21,41 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.transition.Transition;
/**
* Indicates that a method is candidate to be called with a {@link Transition}.
* <p>
* A method annotated with @OnTransition may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnTransition {
/**
* The source states.
*
* @return the source states.
*/
String[] source() default {};
/**
* The target states.
*
* @return the target states.
*/
String[] target() default {};
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.transition.Transition;
/**
* Indicates that a method is candidate to be called with a {@link Transition}.
* <p>
* A method annotated with @OnTransitionEnd may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnTransitionEnd {
/**
* The source states.
*
* @return the source states.
*/
String[] source() default {};
/**
* The target states.
*
* @return the target states.
*/
String[] target() default {};
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2015 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.transition.Transition;
/**
* Indicates that a method is candidate to be called with a {@link Transition}.
* <p>
* A method annotated with @OnTransitionStart may accept a parameter of type
* {@link ExtendedState} or {@link Map} if map argument is itself is annotated
* with {@link EventHeaders}.
* <p>
* Return value can be anything and is effectively discarded.
*
* @author Janne Valkealahti
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnTransitionStart {
/**
* The source states.
*
* @return the source states.
*/
String[] source() default {};
/**
* The target states.
*
* @return the target states.
*/
String[] target() default {};
}

View File

@@ -23,15 +23,8 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.annotation.OrderUtils;
import org.springframework.statemachine.annotation.OnTransition;
/**
* Post-processor for Methods annotated with {@link OnTransition}.
*
* @author Janne Valkealahti
*
*/
public class StateMachineActivatorAnnotationPostProcessor implements MethodAnnotationPostProcessor<OnTransition>{
public class StateMachineActivatorAnnotationPostProcessor<T extends Annotation> implements MethodAnnotationPostProcessor<T>{
protected final BeanFactory beanFactory;
@@ -40,8 +33,8 @@ public class StateMachineActivatorAnnotationPostProcessor implements MethodAnnot
}
@Override
public Object postProcess(Class<?> beanClass, Object bean, String beanName, Method method, OnTransition metaAnnotation, Annotation annotation) {
StateMachineHandler<Object, Object> handler = new StateMachineOnTransitionHandler<Object, Object>(beanClass, bean, method, metaAnnotation, annotation);
public Object postProcess(Class<?> beanClass, Object bean, String beanName, Method method, T metaAnnotation, Annotation annotation) {
StateMachineHandler<T, Object, Object> handler = new StateMachineHandler<T, Object, Object>(beanClass, bean, method, metaAnnotation, annotation);
Integer order = findOrder(bean, method);
if (order != null) {

View File

@@ -41,7 +41,17 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.statemachine.annotation.OnEventNotAccepted;
import org.springframework.statemachine.annotation.OnExtendedStateChanged;
import org.springframework.statemachine.annotation.OnStateChanged;
import org.springframework.statemachine.annotation.OnStateEntry;
import org.springframework.statemachine.annotation.OnStateExit;
import org.springframework.statemachine.annotation.OnStateMachineError;
import org.springframework.statemachine.annotation.OnStateMachineStart;
import org.springframework.statemachine.annotation.OnStateMachineStop;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.OnTransitionEnd;
import org.springframework.statemachine.annotation.OnTransitionStart;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -93,7 +103,28 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B
@Override
public void afterPropertiesSet() {
Assert.notNull(beanFactory, "BeanFactory must not be null");
postProcessors.put(OnTransition.class, new StateMachineActivatorAnnotationPostProcessor(beanFactory));
postProcessors.put(OnTransition.class,
new StateMachineActivatorAnnotationPostProcessor<OnTransition>(beanFactory));
postProcessors.put(OnTransitionStart.class,
new StateMachineActivatorAnnotationPostProcessor<OnTransitionStart>(beanFactory));
postProcessors.put(OnTransitionEnd.class,
new StateMachineActivatorAnnotationPostProcessor<OnTransitionEnd>(beanFactory));
postProcessors.put(OnStateChanged.class,
new StateMachineActivatorAnnotationPostProcessor<OnStateChanged>(beanFactory));
postProcessors.put(OnStateEntry.class,
new StateMachineActivatorAnnotationPostProcessor<OnStateEntry>(beanFactory));
postProcessors.put(OnStateExit.class,
new StateMachineActivatorAnnotationPostProcessor<OnStateExit>(beanFactory));
postProcessors.put(OnStateMachineStart.class,
new StateMachineActivatorAnnotationPostProcessor<OnStateMachineStart>(beanFactory));
postProcessors.put(OnStateMachineStop.class,
new StateMachineActivatorAnnotationPostProcessor<OnStateMachineStop>(beanFactory));
postProcessors.put(OnEventNotAccepted.class,
new StateMachineActivatorAnnotationPostProcessor<OnEventNotAccepted>(beanFactory));
postProcessors.put(OnStateMachineError.class,
new StateMachineActivatorAnnotationPostProcessor<OnStateMachineError>(beanFactory));
postProcessors.put(OnExtendedStateChanged.class,
new StateMachineActivatorAnnotationPostProcessor<OnExtendedStateChanged>(beanFactory));
}
@Override
@@ -115,51 +146,42 @@ public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, B
@SuppressWarnings({ "unchecked", "rawtypes" })
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
for (Annotation annotation : annotations) {
Annotation metaAnnotation = null;
for (Class<? extends Annotation> ppa : postProcessors.keySet()) {
Annotation a = AnnotationUtils.getAnnotation(annotation,ppa);
if (a != null && annotation.getClass().equals(a.getClass())) {
metaAnnotation = a;
} else {
metaAnnotation = a;
}
for (Class<? extends Annotation> ppa : postProcessors.keySet()) {
Annotation metaAnnotation = AnnotationUtils.findAnnotation(method, ppa);
if (metaAnnotation == null) {
continue;
}
for (Annotation a : AnnotationUtils.getAnnotations(method)) {
MethodAnnotationPostProcessor postProcessor = metaAnnotation != null ? postProcessors
.get(metaAnnotation.annotationType()) : null;
if (postProcessor != null && shouldCreateHandler(a)) {
Object result = postProcessor.postProcess(beanClass, bean, beanName, method, metaAnnotation, a);
if (result != null && result instanceof StateMachineHandler) {
String endpointBeanName = generateBeanName(beanName, method, a.annotationType());
MethodAnnotationPostProcessor postProcessor = metaAnnotation != null ? postProcessors
.get(metaAnnotation.annotationType()) : null;
if (postProcessor != null && shouldCreateHandler(annotation)) {
Object result = postProcessor.postProcess(beanClass, bean, beanName, method, metaAnnotation, annotation);
if (result != null && result instanceof StateMachineHandler) {
String endpointBeanName = generateBeanName(beanName, method, annotation.annotationType());
if (result instanceof BeanNameAware) {
((BeanNameAware) result).setBeanName(endpointBeanName);
}
beanFactory.registerSingleton(endpointBeanName, result);
if (result instanceof BeanFactoryAware) {
((BeanFactoryAware) result).setBeanFactory(beanFactory);
}
if (result instanceof InitializingBean) {
try {
((InitializingBean) result).afterPropertiesSet();
} catch (Exception e) {
throw new BeanInitializationException("failed to initialize annotated component", e);
if (result instanceof BeanNameAware) {
((BeanNameAware) result).setBeanName(endpointBeanName);
}
}
if (result instanceof Lifecycle) {
lifecycles.add((Lifecycle) result);
if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) {
((SmartLifecycle) result).start();
beanFactory.registerSingleton(endpointBeanName, result);
if (result instanceof BeanFactoryAware) {
((BeanFactoryAware) result).setBeanFactory(beanFactory);
}
if (result instanceof InitializingBean) {
try {
((InitializingBean) result).afterPropertiesSet();
} catch (Exception e) {
throw new BeanInitializationException("failed to initialize annotated component", e);
}
}
if (result instanceof Lifecycle) {
lifecycles.add((Lifecycle) result);
if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) {
((SmartLifecycle) result).start();
}
}
if (result instanceof ApplicationListener) {
listeners.add((ApplicationListener) result);
}
}
if (result instanceof ApplicationListener) {
listeners.add((ApplicationListener) result);
}
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.statemachine.processor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.core.Ordered;
@@ -28,15 +29,16 @@ import org.springframework.statemachine.annotation.WithStateMachine;
*
* @author Janne Valkealahti
*
* @param <T> the type of annotation
* @param <S> the type of state
* @param <E> the type of event
*/
public class StateMachineHandler<S, E> implements Ordered {
public class StateMachineHandler<T extends Annotation, S, E> implements Ordered {
private final Class<?> beanClass;
private final StateMachineRuntimeProcessor<?, S, E> processor;
private final T metaAnnotation;
private final Annotation annotation;
private int order = Ordered.LOWEST_PRECEDENCE;
/**
@@ -44,10 +46,12 @@ public class StateMachineHandler<S, E> implements Ordered {
*
* @param beanClass the bean class
* @param target the target bean
* @param metaAnnotation the meta annotation
* @param annotation the annotation
* @param method the method
*/
public StateMachineHandler(Class<?> beanClass, Object target, Method method) {
this(beanClass, new MethodInvokingStateMachineRuntimeProcessor<Object, S, E>(target, method));
public StateMachineHandler(Class<?> beanClass, Object target, Method method, T metaAnnotation, Annotation annotation) {
this(beanClass, metaAnnotation, annotation, new MethodInvokingStateMachineRuntimeProcessor<T, S, E>(target, method));
}
/**
@@ -56,31 +60,55 @@ public class StateMachineHandler<S, E> implements Ordered {
* @param beanClass the bean class
* @param target the target bean
* @param methodName the method name
* @param metaAnnotation the meta annotation
* @param annotation the annotation
*/
public StateMachineHandler(Class<?> beanClass, Object target, String methodName) {
this(beanClass, new MethodInvokingStateMachineRuntimeProcessor<Object, S, E>(target, methodName));
public StateMachineHandler(Class<?> beanClass, Object target, String methodName, T metaAnnotation, Annotation annotation) {
this(beanClass, metaAnnotation, annotation, new MethodInvokingStateMachineRuntimeProcessor<T, S, E>(target, methodName));
}
/**
* Instantiates a new container handler.
*
* @param <T> the generic type
* @param beanClass the bean class
* @param metaAnnotation the meta annotation
* @param annotation the annotation
* @param processor the processor
*/
public <T> StateMachineHandler(Class<?> beanClass, MethodInvokingStateMachineRuntimeProcessor<T, S, E> processor) {
public StateMachineHandler(Class<?> beanClass, T metaAnnotation, Annotation annotation,
MethodInvokingStateMachineRuntimeProcessor<T, S, E> processor) {
this.beanClass = beanClass;
this.processor = processor;
this.metaAnnotation = metaAnnotation;
this.annotation = annotation;
}
@Override
public int getOrder() {
return order;
}
/**
* Gets the meta annotation.
*
* @return the meta annotation
*/
public T getMetaAnnotation() {
return metaAnnotation;
}
/**
* Gets the annotation.
*
* @return the annotation
*/
public Annotation getAnnotation() {
return annotation;
}
/**
* Gets the bean class.
*
*
* @return the bean class
*/
public Class<?> getBeanClass() {

View File

@@ -0,0 +1,356 @@
/*
* Copyright 2015 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.processor;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.annotation.OnEventNotAccepted;
import org.springframework.statemachine.annotation.OnExtendedStateChanged;
import org.springframework.statemachine.annotation.OnStateChanged;
import org.springframework.statemachine.annotation.OnStateEntry;
import org.springframework.statemachine.annotation.OnStateExit;
import org.springframework.statemachine.annotation.OnStateMachineError;
import org.springframework.statemachine.annotation.OnStateMachineStart;
import org.springframework.statemachine.annotation.OnStateMachineStop;
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.state.State;
import org.springframework.statemachine.support.StateMachineUtils;
import org.springframework.statemachine.transition.Transition;
import org.springframework.util.Assert;
/**
* Helper class which is used from a StateMachineObjectSupport to ease handling
* of StateMachineHandlers and provides needed caching so that a runtime calls
* are fast. Also provides dedicated methods for each annotated methods so that
* parameters are handled accordingly.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class StateMachineHandlerCallHelper<S, E> implements InitializingBean, BeanFactoryAware {
private final Log log = LogFactory.getLog(StateMachineHandlerCallHelper.class);
private final Map<String, List<CacheEntry>> cache = new HashMap<>();
private ListableBeanFactory beanFactory;
@SuppressWarnings("unchecked")
@Override
public void afterPropertiesSet() throws Exception {
if (!(beanFactory instanceof ListableBeanFactory)) {
log.info("Beanfactory is not instance of ListableBeanFactory, was " + beanFactory + " thus Disabling handlers.");
return;
}
for (StateMachineHandler<? extends Annotation, S, E> handler : beanFactory.getBeansOfType(StateMachineHandler.class).values()) {
Annotation annotation = handler.getAnnotation();
Annotation metaAnnotation = handler.getMetaAnnotation();
WithStateMachine withStateMachine = AnnotationUtils.findAnnotation(handler.getBeanClass(),
WithStateMachine.class);
String statemachineBeanName = withStateMachine.name();
String key = metaAnnotation.annotationType().getName() + statemachineBeanName;
List<CacheEntry> list = cache.get(key);
if (list == null) {
list = new ArrayList<>();
cache.put(key, list);
}
list.add(new CacheEntry(handler, annotation, metaAnnotation));
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.state(beanFactory instanceof ListableBeanFactory,
"Bean factory must be instance of ListableBeanFactory, was " + beanFactory);
this.beanFactory = (ListableBeanFactory)beanFactory;
}
public void callOnStateChanged(String stateMachineId, Transition<S,E> transition, Message<E> message, 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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
if (annotationHandlerSourceTargetMatch((String[]) AnnotationUtils.getValue(entry.metaAnnotation, "source"),
(String[]) AnnotationUtils.getValue(entry.metaAnnotation, "target"), entry.annotation, stateContext.getSource(),
stateContext.getTarget())) {
handlersList.add(entry.handler);
}
}
getStateMachineHandlerResults(handlersList, stateContext);
}
public void callOnStateEntry(String stateMachineId, Transition<S,E> transition, Message<E> message, 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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
if (annotationHandlerSourceTargetMatch((String[]) AnnotationUtils.getValue(entry.metaAnnotation, "source"),
(String[]) AnnotationUtils.getValue(entry.metaAnnotation, "target"), entry.annotation, stateContext.getSource(),
stateContext.getTarget())) {
handlersList.add(entry.handler);
}
}
getStateMachineHandlerResults(handlersList, stateContext);
}
public void callOnStateExit(String stateMachineId, Transition<S,E> transition, Message<E> message, 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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
if (annotationHandlerSourceTargetMatch((String[]) AnnotationUtils.getValue(entry.metaAnnotation, "source"),
(String[]) AnnotationUtils.getValue(entry.metaAnnotation, "target"), entry.annotation, stateContext.getSource(),
stateContext.getTarget())) {
handlersList.add(entry.handler);
}
}
getStateMachineHandlerResults(handlersList, stateContext);
}
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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
handlersList.add(entry.handler);
}
getStateMachineHandlerResults(handlersList, stateContext);
}
public void callOnTransitionStart(String stateMachineId, Transition<S,E> transition, Message<E> message, 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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
if (annotationHandlerSourceTargetMatch((String[]) AnnotationUtils.getValue(entry.metaAnnotation, "source"),
(String[]) AnnotationUtils.getValue(entry.metaAnnotation, "target"), entry.annotation, transition.getSource(),
transition.getTarget())) {
handlersList.add(entry.handler);
}
}
getStateMachineHandlerResults(handlersList, stateContext);
}
public void callOnTransition(String stateMachineId, Transition<S,E> transition, Message<E> message, 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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
if (annotationHandlerSourceTargetMatch((String[]) AnnotationUtils.getValue(entry.metaAnnotation, "source"),
(String[]) AnnotationUtils.getValue(entry.metaAnnotation, "target"), entry.annotation, transition.getSource(),
transition.getTarget())) {
handlersList.add(entry.handler);
}
}
getStateMachineHandlerResults(handlersList, stateContext);
}
public void callOnTransitionEnd(String stateMachineId, Transition<S,E> transition, Message<E> message, 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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
if (annotationHandlerSourceTargetMatch((String[]) AnnotationUtils.getValue(entry.metaAnnotation, "source"),
(String[]) AnnotationUtils.getValue(entry.metaAnnotation, "target"), entry.annotation, transition.getSource(),
transition.getTarget())) {
handlersList.add(entry.handler);
}
}
getStateMachineHandlerResults(handlersList, stateContext);
}
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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
handlersList.add(entry.handler);
}
getStateMachineHandlerResults(handlersList, stateContext);
}
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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
handlersList.add(entry.handler);
}
getStateMachineHandlerResults(handlersList, stateContext);
}
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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
handlersList.add(entry.handler);
}
getStateMachineHandlerResults(handlersList, stateContext);
}
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);
if (list == null) {
return;
}
for (CacheEntry entry : list) {
if (annotationHandlerVariableMatch(entry.metaAnnotation, key)) {
handlersList.add(entry.handler);
}
}
getStateMachineHandlerResults(handlersList, stateContext);
}
private boolean annotationHandlerVariableMatch(Annotation annotation, Object key) {
boolean handle = false;
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation);
Object object = annotationAttributes.get("key");
Collection<String> scoll = StateMachineUtils.toStringCollection(object);
if (!scoll.isEmpty()) {
if (StateMachineUtils.containsAtleastOne(scoll, StateMachineUtils.toStringCollection(key))) {
handle = true;
}
} else {
handle = true;
}
return handle;
}
private boolean annotationHandlerSourceTargetMatch(String[] msources, String[] mtargets, Annotation methodAnnotation,
State<S, E> sourceState, State<S, E> targetState) {
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(methodAnnotation);
Object source = annotationAttributes.get("source");
Object target = annotationAttributes.get("target");
Collection<String> scoll = StateMachineUtils.toStringCollection(source);
if (scoll.isEmpty() && msources != null) {
scoll = Arrays.asList(msources);
}
Collection<String> tcoll = StateMachineUtils.toStringCollection(target);
if (tcoll.isEmpty() && mtargets != null) {
tcoll = Arrays.asList(mtargets);
}
boolean handle = false;
if (!scoll.isEmpty() && !tcoll.isEmpty()) {
if (sourceState != null
&& targetState != null
&& StateMachineUtils.containsAtleastOne(scoll,
StateMachineUtils.toStringCollection(sourceState.getIds()))
&& StateMachineUtils.containsAtleastOne(tcoll,
StateMachineUtils.toStringCollection(targetState.getIds()))) {
handle = true;
}
} else if (!scoll.isEmpty()) {
if (sourceState != null
&& StateMachineUtils.containsAtleastOne(scoll,
StateMachineUtils.toStringCollection(sourceState.getIds()))) {
handle = true;
}
} else if (!tcoll.isEmpty()) {
if (targetState != null
&& StateMachineUtils.containsAtleastOne(tcoll,
StateMachineUtils.toStringCollection(targetState.getIds()))) {
handle = true;
}
} else if (scoll.isEmpty() && tcoll.isEmpty()) {
handle = true;
}
return handle;
}
private List<Object> getStateMachineHandlerResults(List<StateMachineHandler<? extends Annotation, S, E>> stateMachineHandlers,
final StateContext<S, E> stateContext) {
StateMachineRuntime<S, E> runtime = new StateMachineRuntime<S, E>() {
@Override
public StateContext<S, E> getStateContext() {
return stateContext;
}
};
List<Object> results = new ArrayList<Object>();
for (StateMachineHandler<? extends Annotation, S, E> handler : stateMachineHandlers) {
results.add(handler.handle(runtime));
}
return results;
}
private class CacheEntry {
final StateMachineHandler<? extends Annotation, S, E> handler;
final Annotation annotation;
final Annotation metaAnnotation;
public CacheEntry(StateMachineHandler<? extends Annotation, S, E> handler, Annotation annotation, Annotation metaAnnotation) {
this.handler = handler;
this.annotation = annotation;
this.metaAnnotation = metaAnnotation;
}
}
}

View File

@@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
@@ -39,7 +40,9 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.annotation.EventHeaders;
import org.springframework.statemachine.annotation.ExtendedStateVariable;
import org.springframework.statemachine.support.AbstractExpressionEvaluator;
import org.springframework.statemachine.support.AnnotatedMethodFilter;
import org.springframework.statemachine.support.FixedMethodFilter;
@@ -431,17 +434,24 @@ public class StateMachineMethodInvokerHelper<T, S, E> extends AbstractExpression
if (annotationType.equals(EventHeaders.class)) {
sb.append("headers");
} else if (annotationType.equals(ExtendedStateVariable.class)) {
AnnotationAttributes annotationAttributes = AnnotationAttributes
.fromMap(AnnotationUtils.getAnnotationAttributes(mappingAnnotation));
String key = annotationAttributes.getAliasedString("value", ExtendedStateVariable.class, null);
sb.append("variables.get('" + key + "')");
}
} else if (ExtendedState.class.isAssignableFrom(parameterType)) {
sb.append("extendedState");
} else if (StateMachine.class.isAssignableFrom(parameterType)) {
sb.append("stateMachine");
}
}
if (hasUnqualifiedMapParameter) {
if (targetParameterType != null && Map.class.isAssignableFrom(this.targetParameterType)) {
throw new IllegalArgumentException(
"Unable to determine payload matching parameter due to ambiguous Map typed parameters. "
+ "Consider adding the @Payload and or @Headers annotations as appropriate.");
+ "Consider adding the @EventHeaders and or @ExtendedStateVariable annotations as appropriate.");
}
}
sb.append(")");
@@ -466,6 +476,14 @@ public class StateMachineMethodInvokerHelper<T, S, E> extends AbstractExpression
+ annotation.annotationType().getName() + "]");
}
match = annotation;
} else if (type.equals(ExtendedStateVariable.class)) {
if (match != null) {
throw new IllegalArgumentException(
"At most one parameter annotation can be provided for message mapping, "
+ "but found two: [" + match.annotationType().getName() + "] and ["
+ annotation.annotationType().getName() + "]");
}
match = annotation;
}
}
return match;
@@ -496,6 +514,14 @@ public class StateMachineMethodInvokerHelper<T, S, E> extends AbstractExpression
return stateContext.getExtendedState();
}
public Map<Object, Object> getVariables() {
return getExtendedState().getVariables();
}
public StateMachine<SS, EE> getStateMachine() {
return stateContext.getStateMachine();
}
}
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2015 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.processor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.statemachine.annotation.OnTransition;
/**
* Transition specific {@link StateMachineHandler}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class StateMachineOnTransitionHandler<S, E> extends StateMachineHandler<S, E> {
private final OnTransition metaAnnotation;
private final Annotation annotation;
/**
* Instantiates a new state machine on transition handler.
*
* @param beanClass the bean class
* @param target the target
* @param method the method
* @param metaAnnotation the meta annotation
* @param annotation the annotation
*/
public StateMachineOnTransitionHandler(Class<?> beanClass, Object target, Method method, OnTransition metaAnnotation, Annotation annotation) {
super(beanClass, target, method);
this.metaAnnotation = metaAnnotation;
this.annotation = annotation;
}
/**
* Gets the meta annotation.
*
* @return the meta annotation
*/
public OnTransition getMetaAnnotation() {
return metaAnnotation;
}
/**
* Gets the annotation.
*
* @return the annotation
*/
public Annotation getAnnotation() {
return annotation;
}
}

View File

@@ -15,15 +15,12 @@
*/
package org.springframework.statemachine.support;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.apache.commons.logging.Log;
@@ -31,9 +28,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.OrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
@@ -45,12 +39,7 @@ import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.access.StateMachineAccessor;
import org.springframework.statemachine.access.StateMachineFunction;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.processor.StateMachineHandler;
import org.springframework.statemachine.processor.StateMachineOnTransitionHandler;
import org.springframework.statemachine.processor.StateMachineRuntime;
import org.springframework.statemachine.region.Region;
import org.springframework.statemachine.state.AbstractState;
import org.springframework.statemachine.state.ForkPseudoState;
@@ -67,7 +56,6 @@ import org.springframework.statemachine.transition.TransitionKind;
import org.springframework.statemachine.trigger.DefaultTriggerContext;
import org.springframework.statemachine.trigger.Trigger;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -101,10 +89,6 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
private volatile PseudoState<S, E> history;
private final Map<String, StateMachineOnTransitionHandler<S, E>> handlers = new HashMap<String, StateMachineOnTransitionHandler<S,E>>();
private volatile boolean handlersInitialized;
private final Map<Trigger<S, E>, Transition<S,E>> triggerToTransitionMap = new HashMap<Trigger<S,E>, Transition<S,E>>();
private final List<Transition<S, E>> triggerlessTransitions = new ArrayList<Transition<S,E>>();
@@ -192,7 +176,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
public boolean sendEvent(Message<E> event) {
if (hasStateMachineError()) {
// TODO: should we throw exception?
notifyEventNotAccepted(event);
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
return false;
}
@@ -200,18 +184,18 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
event = getStateMachineInterceptors().preEvent(event, this);
} catch (Exception e) {
log.info("Event " + event + " threw exception in interceptors, not accepting event");
notifyEventNotAccepted(event);
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
return false;
}
if (isComplete() || !isRunning()) {
notifyEventNotAccepted(event);
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
return false;
}
boolean accepted = acceptEvent(event);
stateMachineExecutor.execute();
if (!accepted) {
notifyEventNotAccepted(event);
notifyEventNotAccepted(event, buildStateContext(null, null, getRelayStateMachine()));
}
return accepted;
}
@@ -232,7 +216,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
extendedState.setExtendedStateChangeListener(new ExtendedStateChangeListener() {
@Override
public void changed(Object key, Object value) {
notifyExtendedStateChanged(key, value);
notifyExtendedStateChanged(key, value, buildStateContext(null, null, getRelayStateMachine()));
}
});
@@ -277,16 +261,17 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
@Override
public void transit(Transition<S, E> t, StateContext<S, E> stateContext, Message<E> queuedMessage) {
notifyTransitionStart(t);
callHandlers(t.getSource(), t.getTarget(), queuedMessage);
StateContext<S, E> stateContext2 = buildStateContext(queuedMessage, null, getRelayStateMachine());
notifyTransitionStart(t, queuedMessage, buildStateContext(queuedMessage, null, getRelayStateMachine()));
notifyTransition(t, queuedMessage, buildStateContext(queuedMessage, null, getRelayStateMachine()));
if (t.getKind() == TransitionKind.INITIAL) {
switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine());
notifyStateMachineStarted(getRelayStateMachine());
notifyStateMachineStarted(getRelayStateMachine(), stateContext2);
} else if (t.getKind() != TransitionKind.INTERNAL) {
switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine());
}
notifyTransition(t);
notifyTransitionEnd(t);
// TODO: looks like events should be called here and anno processing earlier
notifyTransitionEnd(t, queuedMessage, buildStateContext(queuedMessage, null, getRelayStateMachine()));
}
});
stateMachineExecutor = executor;
@@ -307,6 +292,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
@Override
protected void doStart() {
super.doStart();
// if state is set assume nothing to do
if (currentState != null) {
if (log.isDebugEnabled()) {
@@ -317,7 +303,8 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
// assume that state was set/reseted so we need to
// dispatch started event which would net getting
// dispatched via executor
notifyStateMachineStarted(getRelayStateMachine());
StateContext<S, E> stateContext = buildStateContext(null, null, getRelayStateMachine());
notifyStateMachineStarted(getRelayStateMachine(), stateContext);
return;
}
registerPseudoStateListener();
@@ -338,7 +325,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
@Override
protected void doStop() {
stateMachineExecutor.stop();
notifyStateMachineStopped(this);
notifyStateMachineStopped(this, buildStateContext(null, null, this));
currentState = null;
initialEnabled = null;
}
@@ -359,7 +346,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
currentError = exception;
}
if (currentError != null) {
notifyStateMachineError(this, currentError);
notifyStateMachineError(this, currentError, buildStateContext(null, null, this));
}
}
@@ -697,6 +684,13 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
return new DefaultStateContext<S, E>(event, messageHeaders, extendedState, transition, stateMachine);
}
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target) {
E event = message != null ? message.getPayload() : null;
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
return new DefaultStateContext<S, E>(event, messageHeaders, extendedState, transition, stateMachine, source, target);
}
private State<S, E> findDeepParent(State<S, E> state) {
for (State<S, E> s : states) {
if (s.getStates().contains(state)) {
@@ -729,7 +723,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
start();
}
entryToState(state, message, transition, stateMachine);
notifyStateChanged(notifyFrom, state);
notifyStateChanged(notifyFrom, state, message, buildStateContext(message, null, getRelayStateMachine(), notifyFrom, state));
nonDeepStatePresent = true;
} else if (currentState == null && StateMachineUtils.isSubstate(findDeep, state)) {
if (exit) {
@@ -741,7 +735,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
start();
}
entryToState(findDeep, message, transition, stateMachine);
notifyStateChanged(notifyFrom, findDeep);
notifyStateChanged(notifyFrom, findDeep, message, buildStateContext(message, null, getRelayStateMachine(), notifyFrom, findDeep));
}
if (currentState != null && !nonDeepStatePresent) {
@@ -854,7 +848,8 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
log.debug("Exit state=[" + state + "]");
state.exit(stateContext);
notifyStateExited(state);
notifyStateExited(state, message, buildStateContext(message, null, getRelayStateMachine(), state, null));
}
private void entryToState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
@@ -882,115 +877,9 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
}
}
notifyStateEntered(state);
notifyStateEntered(state, message, buildStateContext(message, null, getRelayStateMachine(), null, state));
log.debug("Enter state=[" + state + "]");
state.entry(stateContext);
}
private void callHandlers(State<S,E> sourceState, State<S,E> targetState, Message<E> message) {
StateContext<S, E> stateContext = buildStateContext(message, null, getRelayStateMachine());
getStateMachineHandlerResults(getStateMachineHandlers(sourceState, targetState), stateContext);
}
private List<Object> getStateMachineHandlerResults(List<StateMachineHandler<S, E>> stateMachineHandlers,
final StateContext<S, E> stateContext) {
StateMachineRuntime<S, E> runtime = new StateMachineRuntime<S, E>() {
@Override
public StateContext<S, E> getStateContext() {
return stateContext;
}
};
List<Object> results = new ArrayList<Object>();
for (StateMachineHandler<S, E> handler : stateMachineHandlers) {
results.add(handler.handle(runtime));
}
return results;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private synchronized List<StateMachineHandler<S, E>> getStateMachineHandlers(State<S, E> sourceState,
State<S, E> targetState) {
BeanFactory beanFactory = getBeanFactory();
// TODO think how to handle null bf
if (beanFactory == null) {
return Collections.emptyList();
}
Assert.state(beanFactory instanceof ListableBeanFactory, "Bean factory must be instance of ListableBeanFactory");
if (!handlersInitialized) {
Map<String, StateMachineOnTransitionHandler> handlersMap = ((ListableBeanFactory) beanFactory)
.getBeansOfType(StateMachineOnTransitionHandler.class);
for (Entry<String, StateMachineOnTransitionHandler> entry : handlersMap.entrySet()) {
handlers.put(entry.getKey(), entry.getValue());
}
handlersInitialized = true;
}
List<StateMachineHandler<S, E>> handlersList = new ArrayList<StateMachineHandler<S, E>>();
for (Entry<String, StateMachineOnTransitionHandler<S, E>> entry : handlers.entrySet()) {
// add only matching names from beanName and WithStateMachine name field
WithStateMachine withStateMachine = AnnotationUtils.findAnnotation(entry.getValue().getBeanClass(), WithStateMachine.class);
if (withStateMachine == null || !ObjectUtils.nullSafeEquals(withStateMachine.name(), getBeanName())) {
continue;
}
OnTransition metaAnnotation = entry.getValue().getMetaAnnotation();
Annotation annotation = entry.getValue().getAnnotation();
if (transitionHandlerMatch(metaAnnotation, annotation, sourceState, targetState)) {
handlersList.add(entry.getValue());
}
}
OrderComparator comparator = new OrderComparator();
Collections.sort(handlersList, comparator);
return handlersList;
}
private boolean transitionHandlerMatch(OnTransition metaAnnotation, Annotation annotation, State<S, E> sourceState, State<S, E> targetState) {
String[] msources = metaAnnotation.source();
String[] mtargets = metaAnnotation.target();
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation);
Object source = annotationAttributes.get("source");
Object target = annotationAttributes.get("target");
Collection<String> scoll = StateMachineUtils.toStringCollection(source);
if (scoll.isEmpty()) {
scoll = Arrays.asList(msources);
}
Collection<String> tcoll = StateMachineUtils.toStringCollection(target);
if (tcoll.isEmpty()) {
tcoll = Arrays.asList(mtargets);
}
boolean handle = false;
if (!scoll.isEmpty() && !tcoll.isEmpty()) {
if (sourceState != null
&& targetState != null
&& StateMachineUtils.containsAtleastOne(scoll,
StateMachineUtils.toStringCollection(sourceState.getIds()))
&& StateMachineUtils.containsAtleastOne(tcoll,
StateMachineUtils.toStringCollection(targetState.getIds()))) {
handle = true;
}
} else if (!scoll.isEmpty()) {
if (sourceState != null
&& StateMachineUtils.containsAtleastOne(scoll,
StateMachineUtils.toStringCollection(sourceState.getIds()))) {
handle = true;
}
} else if (!tcoll.isEmpty()) {
if (targetState != null
&& StateMachineUtils.containsAtleastOne(tcoll,
StateMachineUtils.toStringCollection(targetState.getIds()))) {
handle = true;
}
} else if (scoll.isEmpty() && tcoll.isEmpty()) {
handle = true;
}
return handle;
}
}

View File

@@ -15,37 +15,48 @@
*/
package org.springframework.statemachine.support;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
public class DefaultStateContext<S, E> implements StateContext<S, E> {
private final E event;
private final MessageHeaders messageHeaders;
private final ExtendedState extendedState;
private final Transition<S,E> transition;
private final StateMachine<S, E> stateMachine;
private final State<S, E> source;
private final State<S, E> target;
public DefaultStateContext(E event, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
this(event, messageHeaders, extendedState, transition, stateMachine, null, null);
}
public DefaultStateContext(E event, MessageHeaders messageHeaders, ExtendedState extendedState, Transition<S,E> transition, StateMachine<S, E> stateMachine, State<S, E> source, State<S, E> target) {
this.event = event;
this.messageHeaders = messageHeaders;
this.extendedState = extendedState;
this.transition = transition;
this.stateMachine = stateMachine;
this.source = source;
this.target = target;
}
@Override
public E getEvent() {
return event;
}
@Override
public Message<E> getMessage() {
return null;
}
@Override
public MessageHeaders getMessageHeaders() {
return messageHeaders;
@@ -76,4 +87,18 @@ public class DefaultStateContext<S, E> implements StateContext<S, E> {
return stateMachine;
}
@Override
public State<S, E> getSource() {
return source;
}
@Override
public State<S, E> getTarget() {
return target;
}
@Override
public Exception getException() {
return null;
}
}

View File

@@ -23,10 +23,12 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.core.OrderComparator;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.event.StateMachineEventPublisher;
import org.springframework.statemachine.listener.CompositeStateMachineListener;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.processor.StateMachineHandlerCallHelper;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
import org.springframework.util.Assert;
@@ -51,10 +53,25 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
/** Flag for application context events */
private boolean contextEventsEnabled = true;
private final StateMachineInterceptorList<S, E> interceptors =
new StateMachineInterceptorList<S, E>();
private final StateMachineInterceptorList<S, E> interceptors = new StateMachineInterceptorList<S, E>();
private String beanName;
private volatile boolean handlersInitialized;
private final StateMachineHandlerCallHelper<S, E> stateMachineHandlerCallHelper = new StateMachineHandlerCallHelper<S, E>();
@Override
protected void doStart() {
super.doStart();
if (!handlersInitialized) {
try {
stateMachineHandlerCallHelper.setBeanFactory(getBeanFactory());
stateMachineHandlerCallHelper.afterPropertiesSet();
} catch (Exception e) {
log.error("Unable to initialize annotation handlers", e);
} finally {
handlersInitialized = true;
}
}
}
@Override
public void setBeanName(String name) {
@@ -111,7 +128,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
return stateListener;
}
protected void notifyStateChanged(State<S,E> source, State<S,E> target) {
protected void notifyStateChanged(State<S,E> source, State<S,E> target, Message<E> message, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnStateChanged(getBeanName(), null, message, stateContext);
stateListener.stateChanged(source, target);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -121,7 +139,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyStateEntered(State<S,E> state) {
protected void notifyStateEntered(State<S,E> state, Message<E> message, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnStateEntry(getBeanName(), null, message, stateContext);
stateListener.stateEntered(state);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -131,7 +150,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyStateExited(State<S,E> state) {
protected void notifyStateExited(State<S,E> state, Message<E> message, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnStateExit(getBeanName(), null, message, stateContext);
stateListener.stateExited(state);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -141,7 +161,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyEventNotAccepted(Message<E> event) {
protected void notifyEventNotAccepted(Message<E> event, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnEventNotAccepted(getBeanName(), stateContext);
stateListener.eventNotAccepted(event);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -151,7 +172,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyTransitionStart(Transition<S,E> transition) {
protected void notifyTransitionStart(Transition<S,E> transition, Message<E> message, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnTransitionStart(getBeanName(), transition, message, stateContext);
stateListener.transitionStarted(transition);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -161,7 +183,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyTransition(Transition<S,E> transition) {
protected void notifyTransition(Transition<S,E> transition, Message<E> message, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnTransition(getBeanName(), transition, message, stateContext);
stateListener.transition(transition);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -171,7 +194,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyTransitionEnd(Transition<S,E> transition) {
protected void notifyTransitionEnd(Transition<S,E> transition, Message<E> message, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnTransitionEnd(getBeanName(), transition, message, stateContext);
stateListener.transitionEnded(transition);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -181,7 +205,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyStateMachineStarted(StateMachine<S, E> stateMachine) {
protected void notifyStateMachineStarted(StateMachine<S, E> stateMachine, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnStateMachineStart(getBeanName(), stateContext);
stateListener.stateMachineStarted(stateMachine);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -191,7 +216,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyStateMachineStopped(StateMachine<S, E> stateMachine) {
protected void notifyStateMachineStopped(StateMachine<S, E> stateMachine, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnStateMachineStop(getBeanName(), stateContext);
stateListener.stateMachineStopped(stateMachine);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -201,7 +227,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyStateMachineError(StateMachine<S, E> stateMachine, Exception exception) {
protected void notifyStateMachineError(StateMachine<S, E> stateMachine, Exception exception, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnStateMachineError(getBeanName(), stateContext);
stateListener.stateMachineError(stateMachine, exception);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();
@@ -211,7 +238,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
}
}
protected void notifyExtendedStateChanged(Object key, Object value) {
protected void notifyExtendedStateChanged(Object key, Object value, StateContext<S, E> stateContext) {
stateMachineHandlerCallHelper.callOnExtendedStateChanged(getBeanName(), key, value, stateContext);
stateListener.extendedStateChanged(key, value);
if (contextEventsEnabled) {
StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher();

View File

@@ -24,6 +24,8 @@ import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.action.Action;
@@ -75,8 +77,10 @@ public class EnumStateMachineTests extends AbstractStateMachineTests {
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
ObjectStateMachine<TestStates, TestEvents> machine = new ObjectStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
machine.start();
@@ -148,8 +152,10 @@ public class EnumStateMachineTests extends AbstractStateMachineTests {
// create machine
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
ObjectStateMachine<TestStates, TestEvents> machine = new ObjectStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
machine.start();
@@ -189,8 +195,10 @@ public class EnumStateMachineTests extends AbstractStateMachineTests {
transitions.add(transitionInternalSI);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
ObjectStateMachine<TestStates, TestEvents> machine = new ObjectStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
machine.start();

View File

@@ -29,6 +29,8 @@ import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -102,9 +104,11 @@ public class RegionMachineTests extends AbstractStateMachineTests {
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
Transition<TestStates,TestEvents> initialTransition = new InitialTransition<TestStates,TestEvents>(stateSI);
ObjectStateMachine<TestStates, TestEvents> machine = new ObjectStateMachine<TestStates, TestEvents>(states, transitions, stateSI, initialTransition, null, null);
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
machine.start();
@@ -131,6 +135,7 @@ public class RegionMachineTests extends AbstractStateMachineTests {
@Test
public void testMultiRegionBuildRaw() throws Exception {
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
State<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI, pseudoState);
@@ -169,6 +174,7 @@ public class RegionMachineTests extends AbstractStateMachineTests {
Transition<TestStates,TestEvents> initialTransition11 = new InitialTransition<TestStates,TestEvents>(stateS111);
ObjectStateMachine<TestStates, TestEvents> machine11 = new ObjectStateMachine<TestStates, TestEvents>(states11, transitions11, stateS111, initialTransition11, null, null);
machine11.setTaskExecutor(taskExecutor);
machine11.setBeanFactory(beanFactory);
machine11.afterPropertiesSet();
Collection<State<TestStates,TestEvents>> states12 = new ArrayList<State<TestStates,TestEvents>>();
@@ -181,6 +187,7 @@ public class RegionMachineTests extends AbstractStateMachineTests {
Transition<TestStates,TestEvents> initialTransition12 = new InitialTransition<TestStates,TestEvents>(stateS121);
ObjectStateMachine<TestStates, TestEvents> machine12 = new ObjectStateMachine<TestStates, TestEvents>(states12, transitions12, stateS121, initialTransition12, null, null);
machine12.setTaskExecutor(taskExecutor);
machine12.setBeanFactory(beanFactory);
machine12.afterPropertiesSet();
Collection<Region<TestStates,TestEvents>> regions = new ArrayList<Region<TestStates,TestEvents>>();
@@ -198,6 +205,7 @@ public class RegionMachineTests extends AbstractStateMachineTests {
ObjectStateMachine<TestStates, TestEvents> machine = new ObjectStateMachine<TestStates, TestEvents>(states, transitions, stateR, initialTransition, null, null);
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
machine.start();

View File

@@ -27,6 +27,8 @@ import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -128,11 +130,15 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
submachine1.setTaskExecutor(taskExecutor);
submachine1.setBeanFactory(beanFactory);
submachine1.afterPropertiesSet();
submachine11.setTaskExecutor(taskExecutor);
submachine11.setBeanFactory(beanFactory);
submachine11.afterPropertiesSet();
machine.start();
@@ -220,9 +226,12 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
submachine11.setTaskExecutor(taskExecutor);
submachine11.setBeanFactory(beanFactory);
submachine11.afterPropertiesSet();
machine.start();
@@ -318,11 +327,15 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
submachine1.setTaskExecutor(taskExecutor);
submachine1.setBeanFactory(beanFactory);
submachine1.afterPropertiesSet();
submachine11.setTaskExecutor(taskExecutor);
submachine11.setBeanFactory(beanFactory);
submachine11.afterPropertiesSet();
machine.start();

View File

@@ -32,7 +32,10 @@ import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.ObjectStateMachine;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
@@ -40,39 +43,124 @@ import org.springframework.statemachine.config.builders.StateMachineTransitionCo
public class MethodAnnotationTests extends AbstractStateMachineTests {
@Override
protected AnnotationConfigApplicationContext buildContext() {
return new AnnotationConfigApplicationContext();
}
@Test
@SuppressWarnings("unchecked")
public void testMethodAnnotations() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BaseConfig.class, BeanConfig1.class, Config1.class);
public void testOnTransition() throws Exception {
context.register(BaseConfig.class, BeanConfig1.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
assertThat(context.containsBean("fooMachine"), is(true));
Bean1 bean1 = context.getBean(Bean1.class);
bean1.reset(1, 1, 1, 1, 1, 1, 1, 1);
machine.start();
assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(false));
assertThat(bean1.onOnTransitionLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean1.onMethod1Count, is(0));
assertThat(bean1.onOnTransitionCount, is(1));
assertThat(bean1.onTransitionFromS1ToS2Latch.await(1, TimeUnit.SECONDS), is(false));
assertThat(bean1.onTransitionLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean1.onTransitionFromS1ToS2Count, is(0));
assertThat(bean1.onTransitionCount, is(1));
bean1.reset(1, 1);
// this event should cause 'method1' to get called
bean1.reset(1, 1, 1, 1, 1, 1, 1, 1);
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean1.onOnTransitionLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean1.onMethod1Count, is(1));
assertThat(bean1.onOnTransitionCount, is(1));
assertThat(bean1.onTransitionFromS1ToS2Latch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean1.onTransitionLatch.await(1, TimeUnit.SECONDS), is(true));
context.close();
assertThat(bean1.onTransitionFromS1ToS2Count, is(1));
assertThat(bean1.onTransitionCount, is(1));
}
@Test
@SuppressWarnings("unchecked")
public void testOnStateChanged() throws Exception {
context.register(BaseConfig.class, BeanConfig1.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
assertThat(context.containsBean("fooMachine"), is(true));
Bean1 bean1 = context.getBean(Bean1.class);
bean1.reset(1, 1, 1, 1, 1, 1, 1, 1);
machine.start();
assertThat(bean1.onStateChangedFromS1ToS2Latch.await(1, TimeUnit.SECONDS), is(false));
assertThat(bean1.onStateChangedLatch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean1.onStateChangedFromS1ToS2Count, is(0));
assertThat(bean1.onStateChangedCount, is(1));
bean1.reset(1, 1, 1, 1, 1, 1, 1, 1);
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(bean1.onStateChangedFromS1ToS2Latch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean1.onStateChangedLatch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean1.onStateChangedFromS1ToS2Count, is(1));
assertThat(bean1.onStateChangedCount, is(1));
}
@Test
@SuppressWarnings("unchecked")
public void testOnStateMachineStartStop() throws Exception {
context.register(BaseConfig.class, BeanConfig1.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
assertThat(context.containsBean("fooMachine"), is(true));
Bean1 bean1 = context.getBean(Bean1.class);
bean1.reset(1, 1, 1, 1, 1, 1, 1, 1);
machine.start();
assertThat(bean1.onStateMachineStartLatch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean1.onStateMachineStartCount, is(1));
assertThat(bean1.onStateMachineStopLatch.await(1, TimeUnit.SECONDS), is(false));
assertThat(bean1.onStateMachineStopCount, is(0));
bean1.reset(1, 1, 1, 1, 1, 1, 1, 1);
machine.stop();
assertThat(bean1.onStateMachineStartLatch.await(1, TimeUnit.SECONDS), is(false));
assertThat(bean1.onStateMachineStartCount, is(0));
assertThat(bean1.onStateMachineStopLatch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean1.onStateMachineStopCount, is(1));
}
@Test
@SuppressWarnings("unchecked")
public void testOnExtendedStateChanged() throws Exception {
context.register(BaseConfig.class, BeanConfig5.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
assertThat(context.containsBean("fooMachine"), is(true));
Bean5 bean5 = context.getBean(Bean5.class);
machine.start();
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("V1", "V1val").build());
assertThat(bean5.onExtendedStateChanged1Latch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean5.onExtendedStateChanged1Count, is(1));
assertThat(bean5.onExtendedStateChanged2Latch.await(1, TimeUnit.SECONDS), is(true));
assertThat(bean5.onExtendedStateChanged2Count, is(1));
assertThat(bean5.onExtendedStateChanged2Value, is("V1val"));
assertThat(bean5.onExtendedStateChangedKeyV2Latch.await(1, TimeUnit.SECONDS), is(false));
assertThat(bean5.onExtendedStateChangedKeyV2Count, is(0));
}
@Test
@SuppressWarnings("unchecked")
public void testMethodAnnotations2() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BaseConfig.class, BeanConfig2.class, Config1.class);
context.register(BaseConfig.class, BeanConfig2.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
@@ -93,35 +181,161 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
assertThat(bean2.onMethod2Latch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean2.variable, notNullValue());
assertThat((String)bean2.variable, is("jee"));
}
context.close();
@Test
@SuppressWarnings("unchecked")
public void testMethodAnnotations3() throws Exception {
context.register(BaseConfig.class, BeanConfig3.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
assertThat(context.containsBean("fooMachine"), is(true));
machine.start();
Bean3 bean3 = context.getBean(Bean3.class);
// this event should cause 'method1' to get called
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(bean3.onStateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
}
@Test
@SuppressWarnings("unchecked")
public void testMethodAnnotations4() throws Exception {
context.register(BaseConfig.class, BeanConfig4.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
assertThat(context.containsBean("fooMachine"), is(true));
Bean4 bean4 = context.getBean(Bean4.class);
machine.start();
assertThat(bean4.onStateEntryLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean4.onStateExitLatch.await(2, TimeUnit.SECONDS), is(false));
assertThat(bean4.onStateEntryCount, is(1));
assertThat(bean4.onStateExitCount, is(0));
bean4.reset(1, 1);
// this event should cause 'method1' to get called
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(bean4.onStateEntryLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean4.onStateExitLatch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean4.onStateEntryCount, is(1));
assertThat(bean4.onStateExitCount, is(1));
}
@Test
@SuppressWarnings("unchecked")
public void testMethodAnnotations5() throws Exception {
context.register(BaseConfig.class, BeanConfig6.class, Config1.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
Bean6 bean6 = context.getBean(Bean6.class);
machine.start();
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).build());
assertThat(bean6.onEventNotAcceptedLatch.await(2, TimeUnit.SECONDS), is(true));
}
@WithStateMachine
static class Bean1 {
CountDownLatch onMethod1Latch = new CountDownLatch(1);
CountDownLatch onOnTransitionLatch = new CountDownLatch(1);
int onMethod1Count;
int onOnTransitionCount;
CountDownLatch onTransitionFromS1ToS2Latch = new CountDownLatch(1);
CountDownLatch onTransitionLatch = new CountDownLatch(1);
CountDownLatch onStateChangedFromS1ToS2Latch = new CountDownLatch(1);
CountDownLatch onStateChangedLatch = new CountDownLatch(1);
CountDownLatch onTransitionEndLatch = new CountDownLatch(1);
CountDownLatch onTransitionStartLatch = new CountDownLatch(1);
CountDownLatch onStateMachineStartLatch = new CountDownLatch(1);
CountDownLatch onStateMachineStopLatch = new CountDownLatch(1);
int onTransitionFromS1ToS2Count = 0;
int onTransitionCount = 0;
int onStateChangedFromS1ToS2Count = 0;
int onStateChangedCount = 0;
int onTransitionEndCount = 0;
int onTransitionStartCount = 0;
int onStateMachineStartCount = 0;
int onStateMachineStopCount = 0;
@OnTransition(source = "S1", target = "S2")
public void method1() {
onMethod1Count++;
onMethod1Latch.countDown();
public void onTransitionFromS1ToS2() {
onTransitionFromS1ToS2Count++;
onTransitionFromS1ToS2Latch.countDown();
}
@OnTransition
public void onTransition() {
onOnTransitionCount++;
onOnTransitionLatch.countDown();
onTransitionCount++;
onTransitionLatch.countDown();
}
public void reset(int a1, int a2) {
onMethod1Latch = new CountDownLatch(a1);
onOnTransitionLatch = new CountDownLatch(a2);
onMethod1Count = 0;
onOnTransitionCount = 0;
@OnTransitionEnd
public void onTransitionEnd() {
onTransitionEndCount++;
onTransitionEndLatch.countDown();
}
@OnTransitionStart
public void onTransitionStart() {
onTransitionStartCount++;
onTransitionStartLatch.countDown();
}
@OnStateChanged(source = "S1", target = "S2")
public void onStateChangedFromS1ToS2() {
onStateChangedFromS1ToS2Count++;
onStateChangedFromS1ToS2Latch.countDown();
}
@OnStateChanged
public void onStateChanged() {
onStateChangedCount++;
onStateChangedLatch.countDown();
}
@OnStateMachineStart
public void onStateMachineStart() {
onStateMachineStartLatch.countDown();
onStateMachineStartCount++;
}
@OnStateMachineStart
public void onStateMachineStartWithParam(StateMachine<TestStates,TestEvents> machine) {
}
@OnStateMachineStop
public void onStateMachineStop() {
onStateMachineStopLatch.countDown();
onStateMachineStopCount++;
}
@OnStateMachineStop
public void onStateMachineStopWithParam(StateMachine<TestStates,TestEvents> machine) {
}
public void reset(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) {
onTransitionFromS1ToS2Latch = new CountDownLatch(a1);
onTransitionLatch = new CountDownLatch(a2);
onStateChangedFromS1ToS2Latch = new CountDownLatch(a3);
onStateChangedLatch = new CountDownLatch(a4);
onTransitionEndLatch = new CountDownLatch(a5);
onTransitionStartLatch = new CountDownLatch(a6);
onStateMachineStartLatch = new CountDownLatch(a7);
onStateMachineStopLatch = new CountDownLatch(a8);
onTransitionFromS1ToS2Count = 0;
onTransitionCount = 0;
onStateChangedFromS1ToS2Count = 0;
onStateChangedCount = 0;
onTransitionEndCount = 0;
onTransitionStartCount = 0;
onStateMachineStartCount = 0;
onStateMachineStopCount = 0;
}
}
@@ -151,6 +365,100 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
}
@WithStateMachine
static class Bean3 {
CountDownLatch onStateChangedLatch = new CountDownLatch(1);
@OnStateChanged
public void onStateChanged() {
onStateChangedLatch.countDown();
}
}
@WithStateMachine
static class Bean4 {
CountDownLatch onStateEntryLatch = new CountDownLatch(1);
CountDownLatch onStateExitLatch = new CountDownLatch(1);
int onStateEntryCount = 0;
int onStateExitCount = 0;
@OnStateEntry
public void onStateEntry() {
onStateEntryCount++;
onStateEntryLatch.countDown();
}
@OnStateExit
public void onStateExit() {
onStateExitCount++;
onStateExitLatch.countDown();
}
public void reset(int a1, int a2) {
onStateEntryCount = 0;
onStateExitCount = 0;
onStateEntryLatch = new CountDownLatch(a1);
onStateExitLatch = new CountDownLatch(a2);
}
}
@WithStateMachine
static class Bean5 {
CountDownLatch onExtendedStateChanged1Latch = new CountDownLatch(1);
CountDownLatch onExtendedStateChanged2Latch = new CountDownLatch(1);
CountDownLatch onExtendedStateChangedKeyV2Latch = new CountDownLatch(1);
int onExtendedStateChanged1Count = 0;
int onExtendedStateChanged2Count = 0;
Object onExtendedStateChanged2Value = null;
int onExtendedStateChangedKeyV2Count = 0;
@OnExtendedStateChanged
public void onExtendedStateChanged1() {
onExtendedStateChanged1Count++;
onExtendedStateChanged1Latch.countDown();
}
@OnExtendedStateChanged
public void onExtendedStateChanged2(@ExtendedStateVariable("V1") Object value) {
onExtendedStateChanged2Value = value;
onExtendedStateChanged2Count++;
onExtendedStateChanged2Latch.countDown();
}
@OnExtendedStateChanged(key = "V2")
public void onExtendedStateChangedKeyV2() {
onExtendedStateChangedKeyV2Count++;
onExtendedStateChangedKeyV2Latch.countDown();
}
public void reset(int a1, int a2, int a3) {
onExtendedStateChanged1Latch = new CountDownLatch(a1);
onExtendedStateChanged2Latch = new CountDownLatch(a2);
onExtendedStateChangedKeyV2Latch = new CountDownLatch(a3);
onExtendedStateChanged1Count = 0;
onExtendedStateChanged2Count = 0;
onExtendedStateChanged2Value = null;
onExtendedStateChangedKeyV2Count = 0;
}
}
@WithStateMachine
static class Bean6 {
CountDownLatch onEventNotAcceptedLatch = new CountDownLatch(1);
@OnEventNotAccepted
public void onEventNotAcceptedLatch() {
onEventNotAcceptedLatch.countDown();
}
}
@Configuration
static class BeanConfig1 {
@@ -171,6 +479,46 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
}
@Configuration
static class BeanConfig3 {
@Bean
public Bean3 bean3() {
return new Bean3();
}
}
@Configuration
static class BeanConfig4 {
@Bean
public Bean4 bean4() {
return new Bean4();
}
}
@Configuration
static class BeanConfig5 {
@Bean
public Bean5 bean5() {
return new Bean5();
}
}
@Configuration
static class BeanConfig6 {
@Bean
public Bean6 bean6() {
return new Bean6();
}
}
@Configuration
@EnableStateMachine(name = {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, "fooMachine"})
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@@ -192,6 +540,7 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
.event(TestEvents.E1)
.guard(testGuard())
.action(testAction())
.action(extendedStateAction())
.and()
.withExternal()
.source(TestStates.S2)
@@ -214,6 +563,20 @@ public class MethodAnnotationTests extends AbstractStateMachineTests {
return new TestAction();
}
@Bean
public Action<TestStates, TestEvents> extendedStateAction() {
return new Action<TestStates, TestEvents>() {
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
String e1 = context.getMessageHeaders().get("V1", String.class);
if (e1 != null) {
context.getExtendedState().getVariables().put("V1", e1);
}
}
};
}
}
}

View File

@@ -363,7 +363,6 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests {
}
@Configuration
@EnableStateMachine
static class Config4 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {

View File

@@ -23,6 +23,8 @@ import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.ObjectStateMachine;
@@ -69,8 +71,10 @@ public class RegionStateTests extends AbstractStateMachineTests {
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
ObjectStateMachine<TestStates, TestEvents> machine = new ObjectStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
machine.start();

View File

@@ -24,6 +24,8 @@ import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
@@ -83,8 +85,10 @@ public class SubmachineStateTests extends AbstractStateMachineTests {
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
BeanFactory beanFactory = new DefaultListableBeanFactory();
ObjectStateMachine<TestStates, TestEvents> machine = new ObjectStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.setBeanFactory(beanFactory);
machine.afterPropertiesSet();
machine.start();