initial import

This commit is contained in:
Janne Valkealahti
2015-02-04 18:17:50 +00:00
parent ded18f863f
commit 48c65fd9ac
155 changed files with 12028 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
/*
* 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;
import java.util.Collection;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.support.AbstractStateMachine;
import org.springframework.statemachine.transition.Transition;
/**
* Specialisation of a {@link StateMachine} using enums
* as its {@link State} and event types.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class EnumStateMachine<S extends Enum<S>, E extends Enum<E>> extends AbstractStateMachine<S, E> {
/**
* Instantiates a new enum state machine.
*
* @param states the states
* @param transitions the transitions
* @param initialState the initial state
*/
public EnumStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState) {
super(states, transitions, initialState);
}
}

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;
import java.util.Map;
/**
* Extended states are used to supplement state machine with a variables. If
* extended state is used a complete condition of a state machine is a
* combination of its state an extended state variables.
*
* @author Janne Valkealahti
*
*/
public interface ExtendedState {
/**
* Gets the extended state variables.
*
* @return the extended state variables
*/
Map<String, Object> getVariables();
}

View File

@@ -0,0 +1,47 @@
/*
* 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;
import org.springframework.messaging.MessageHeaders;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.transition.Transition;
/**
* {@code StateContext} is representing a current context used in
* {@link Transition}s, {@link Action}s and {@link Guard}s order to get access
* to event headers and {@link ExtendedState}.
*
* @author Janne Valkealahti
*
*/
public interface StateContext {
/**
* Gets the event message headers.
*
* @return the event message headers
*/
MessageHeaders getMessageHeaders();
/**
* Gets the state machine extended state.
*
* @return the state machine extended state
*/
ExtendedState getExtendedState();
}

View File

@@ -0,0 +1,73 @@
/*
* 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;
import org.springframework.messaging.Message;
import org.springframework.statemachine.listener.StateMachineListener;
/**
* {@code StateMachine} provides an APIs for generic finite state machine needed
* for basic operations like working with states, events and a lifecycle.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface StateMachine<S, E> {
/**
* Gets the initial state {@code S}.
*
* @return initial state
*/
S getInitialState();
/**
* Gets the current state {@code S}.
*
* @return current state
*/
S getState();
/**
* Start the state machine.
*/
void start();
/**
* Send an event {@code E} wrapped with a {@link Message} to the state
* machine.
*
* @param event the wrapped event to send
*/
void sendEvent(Message<E> event);
/**
* Send an event {@code E} to the state machine.
*
* @param event the event to send
*/
void sendEvent(E event);
/**
* Adds the state listener.
*
* @param listener the listener
*/
void addStateListener(StateMachineListener<S, E> listener);
}

View File

@@ -0,0 +1,32 @@
/*
* 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;
/**
* Various constants used in state machine lib.
*
* @author Janne Valkealahti
*
*/
public abstract class StateMachineSystemConstants {
/** Default bean id for state machine. */
public static final String DEFAULT_ID_STATEMACHINE = "stateMachine";
/** Default bean id for state machine factory. */
public static final String DEFAULT_ID_STATEMACHINEFACTORY = "stateMachineFactory";
}

View File

@@ -0,0 +1,36 @@
/*
* 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.action;
import org.springframework.statemachine.StateContext;
/**
* Generic strategy interface used by a state machine to respond
* events by executing an {@code Action} with a {@link StateContext}.
*
* @author Janne Valkealahti
*
*/
public interface Action {
/**
* Execute action with a {@link StateContext}.
*
* @param context the state context
*/
void execute(StateContext context);
}

View File

@@ -0,0 +1,35 @@
/*
* 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;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OnTransition {
String source() default "";
String target() default "";
}

View File

@@ -0,0 +1,36 @@
/*
* 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.stereotype.Component;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Component
public @interface WithStateMachine {
String name() 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.config;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration;
import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration;
import org.springframework.statemachine.config.configuration.StateMachineConfiguration;
/**
* Example annotation which imports @{@link Configuration}s.
*
* @author Janne Valkealahti
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@EnableAnnotationConfiguration
@Import({StateMachineConfiguration.class,ObjectPostProcessorConfiguration.class})
public @interface EnableStateMachine {
/**
* The name of bean, or if plural, aliases for bean created based on this
* annotation. If left unspecified bean name will be autogenerated.
*
* @see Bean#name()
* @return the array if names or empty as default
*/
String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE};
}

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.config;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration;
import org.springframework.statemachine.config.common.annotation.configuration.ObjectPostProcessorConfiguration;
import org.springframework.statemachine.config.configuration.StateMachineFactoryConfiguration;
/**
* Example annotation which imports @{@link Configuration}s.
*
* @author Janne Valkealahti
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@EnableAnnotationConfiguration
@Import({StateMachineFactoryConfiguration.class,ObjectPostProcessorConfiguration.class})
public @interface EnableStateMachineFactory {
/**
* The name of bean, or if plural, aliases for bean created based on this
* annotation. If left unspecified bean name will be autogenerated.
*
* @see Bean#name()
* @return the array if names or empty as default
*/
String[] name() default {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY};
}

View File

@@ -0,0 +1,20 @@
/*
* 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.config;
public class EnumStateMachineConfigurerAdapter<S extends Enum<S>, E extends Enum<E>> extends StateMachineConfigurerAdapter<S, E> {
}

View File

@@ -0,0 +1,103 @@
/*
* 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.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.statemachine.EnumStateMachine;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.builders.StateMachineStates;
import org.springframework.statemachine.config.builders.StateMachineTransitions;
import org.springframework.statemachine.config.builders.StateMachineStates.StateData;
import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData;
import org.springframework.statemachine.state.EnumState;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.support.LifecycleObjectSupport;
import org.springframework.statemachine.transition.DefaultExternalTransition;
import org.springframework.statemachine.transition.DefaultInternalTransition;
import org.springframework.statemachine.transition.Transition;
import org.springframework.statemachine.transition.TransitionKind;
/**
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> extends LifecycleObjectSupport implements
StateMachineFactory<State<S, E>, E> {
private final StateMachineTransitions<S, E> stateMachineTransitions;
private final StateMachineStates<S, E> stateMachineStates;
/**
* Instantiates a new enum state machine factory.
*
* @param stateMachineTransitions the state machine transitions
* @param stateMachineStates the state machine states
*/
public EnumStateMachineFactory(StateMachineTransitions<S, E> stateMachineTransitions,
StateMachineStates<S, E> stateMachineStates) {
this.stateMachineTransitions = stateMachineTransitions;
this.stateMachineStates = stateMachineStates;
}
@Override
public StateMachine<State<S, E>, E> getStateMachine() {
return stateMachine();
}
public StateMachine<State<S, E>, E> stateMachine() {
Map<S, State<S, E>> stateMap = new HashMap<S, State<S, E>>();
for (StateData<S, E> stateData : stateMachineStates.getStates()) {
stateMap.put(stateData.getState(), new EnumState<S, E>(stateData.getState(), stateData.getDeferred(),
stateData.getEntryActions(), stateData.getExitActions()));
}
Collection<Transition<S, E>> transitions = new ArrayList<Transition<S, E>>();
for (TransitionData<S, E> transitionData : stateMachineTransitions.getTransitions()) {
S source = transitionData.getSource();
S target = transitionData.getTarget();
E event = transitionData.getEvent();
if (transitionData.getKind() == TransitionKind.EXTERNAL) {
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(stateMap.get(source),
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard());
transitions.add(transition);
} else if (transitionData.getKind() == TransitionKind.INTERNAL) {
DefaultInternalTransition<S, E> transition = new DefaultInternalTransition<S, E>(stateMap.get(source),
transitionData.getActions(), event, transitionData.getGuard());
transitions.add(transition);
}
}
EnumStateMachine<S, E> machine = new EnumStateMachine<S, E>(stateMap.values(), transitions,
stateMap.get(stateMachineStates.getInitialState()));
machine.afterPropertiesSet();
if (getBeanFactory() != null) {
machine.setBeanFactory(getBeanFactory());
}
machine.setAutoStartup(isAutoStartup());
machine.start();
return machine;
}
}

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.config;
import org.springframework.statemachine.config.builders.StateMachineStates;
import org.springframework.statemachine.config.builders.StateMachineTransitions;
public class StateMachineConfig<S, E> {
public final StateMachineTransitions<S, E> transitions;
public final StateMachineStates<S, E> states;
public StateMachineConfig(StateMachineTransitions<S, E> transitions, StateMachineStates<S, E> states) {
this.transitions = transitions;
this.states = states;
}
public StateMachineTransitions<S, E> getTransitions() {
return transitions;
}
public StateMachineStates<S, E> getStates() {
return states;
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.config;
import org.springframework.statemachine.config.builders.StateMachineConfigBuilder;
import org.springframework.statemachine.config.builders.StateMachineConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateBuilder;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
public class StateMachineConfigurerAdapter<S, E> implements StateMachineConfigurer<S, E> {
private StateMachineTransitionBuilder<S, E> transitionBuilder;
private StateMachineStateBuilder<S, E> stateBuilder;
@Override
public final void init(StateMachineConfigBuilder<S, E> config) throws Exception {
config.setSharedObject(StateMachineTransitionBuilder.class, getStateMachineTransitionBuilder());
config.setSharedObject(StateMachineStateBuilder.class, getStateMachineStateBuilder());
}
@Override
public void configure(StateMachineConfigBuilder<S, E> config) throws Exception {
}
@Override
public void configure(StateMachineStateConfigurer<S, E> states) throws Exception {
}
@Override
public void configure(StateMachineTransitionConfigurer<S, E> transitions) throws Exception {
}
@Override
public boolean isAssignable(AnnotationBuilder<StateMachineConfig<S, E>> builder) {
return builder instanceof StateMachineConfigBuilder;
}
protected final StateMachineTransitionBuilder<S, E> getStateMachineTransitionBuilder() throws Exception {
if (transitionBuilder != null) {
return transitionBuilder;
}
transitionBuilder = new StateMachineTransitionBuilder<S, E>(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true);
configure(transitionBuilder);
return transitionBuilder;
}
protected final StateMachineStateBuilder<S, E> getStateMachineStateBuilder() throws Exception {
if (stateBuilder != null) {
return stateBuilder;
}
stateBuilder = new StateMachineStateBuilder<S, E>(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true);
configure(stateBuilder);
return stateBuilder;
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.config;
import org.springframework.statemachine.StateMachine;
public interface StateMachineFactory<S, E> {
StateMachine<S, E> getStateMachine();
}

View File

@@ -0,0 +1,36 @@
/*
* 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.config.builders;
import org.springframework.statemachine.config.StateMachineConfig;
import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder;
public class StateMachineConfigBuilder<S, E>
extends
AbstractConfiguredAnnotationBuilder<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>, StateMachineConfigBuilder<S, E>> {
@SuppressWarnings("unchecked")
@Override
protected StateMachineConfig<S, E> performBuild() throws Exception {
StateMachineTransitionBuilder<?, ?> sharedObject = getSharedObject(StateMachineTransitionBuilder.class);
StateMachineStateBuilder<?, ?> sharedObject2 = getSharedObject(StateMachineStateBuilder.class);
StateMachineStates<S, E> states = (StateMachineStates<S, E>) sharedObject2.build();
StateMachineTransitions<S, E> transitions = (StateMachineTransitions<S, E>) sharedObject.build();
StateMachineConfig<S, E> bean = new StateMachineConfig<S, E>(transitions, states);
return bean;
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.config.builders;
import org.springframework.statemachine.config.StateMachineConfig;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer;
public interface StateMachineConfigurer<S, E> extends
AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>> {
void configure(StateMachineStateConfigurer<S, E> transitions) throws Exception;
void configure(StateMachineTransitionConfigurer<S, E> transitions) throws Exception;
}

View File

@@ -0,0 +1,66 @@
/*
* 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.config.builders;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.statemachine.config.builders.StateMachineStates.StateData;
import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder;
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
import org.springframework.statemachine.config.configurers.DefaultStateConfigurer;
import org.springframework.statemachine.config.configurers.StateConfigurer;
public class StateMachineStateBuilder<S, E>
extends AbstractConfiguredAnnotationBuilder<StateMachineStates<S, E>, StateMachineStateConfigurer<S, E>, StateMachineStateBuilder<S, E>>
implements StateMachineStateConfigurer<S, E> {
private Collection<StateData<S, E>> states = new ArrayList<StateData<S, E>>();
private S initialState;
public StateMachineStateBuilder() {
super();
}
public StateMachineStateBuilder(ObjectPostProcessor<Object> objectPostProcessor,
boolean allowConfigurersOfSameType) {
super(objectPostProcessor, allowConfigurersOfSameType);
}
public StateMachineStateBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
@Override
protected StateMachineStates<S, E> performBuild() throws Exception {
StateMachineStates<S, E> bean = new StateMachineStates<S, E>(initialState, states);
return bean;
}
@Override
public StateConfigurer<S, E> withStates() throws Exception {
return apply(new DefaultStateConfigurer<S, E>());
}
public void add(Collection<StateData<S, E>> states) {
this.states.addAll(states);
}
public void setInitialState(S state) {
this.initialState = state;
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.config.builders;
import org.springframework.statemachine.config.configurers.StateConfigurer;
public interface StateMachineStateConfigurer<S, E> {
StateConfigurer<S, E> withStates() throws Exception;
}

View File

@@ -0,0 +1,74 @@
/*
* 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.config.builders;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
public class StateMachineStates<S, E> {
private Collection<StateData<S, E>> states;
private final S initialState;
public StateMachineStates(S initialState, Collection<StateData<S, E>> states) {
this.states = states;
this.initialState = initialState;
}
public Collection<StateData<S, E>> getStates() {
return states;
}
public S getInitialState() {
return initialState;
}
public static class StateData<S, E> {
private S state;
private Collection<E> deferred;
private Collection<Action> entryActions;
private Collection<Action> exitActions;
public StateData(S state, Collection<E> deferred) {
this(state, deferred, null, null);
}
public StateData(S state, Collection<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions) {
this.state = state;
this.deferred = deferred;
this.entryActions = entryActions;
this.exitActions = exitActions;
}
public StateData(S state, E[] deferred) {
this(state, deferred != null ? Arrays.asList(deferred) : new ArrayList<E>());
}
public S getState() {
return state;
}
public Collection<E> getDeferred() {
return deferred;
}
public Collection<Action> getEntryActions() {
return entryActions;
}
public Collection<Action> getExitActions() {
return exitActions;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.config.builders;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData;
import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder;
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.transition.TransitionKind;
public class StateMachineTransitionBuilder<S, E>
extends
AbstractConfiguredAnnotationBuilder<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
implements StateMachineTransitionConfigurer<S, E> {
private Collection<TransitionData<S, E>> transitionData = new ArrayList<TransitionData<S, E>>();
public StateMachineTransitionBuilder() {
super();
}
public StateMachineTransitionBuilder(ObjectPostProcessor<Object> objectPostProcessor,
boolean allowConfigurersOfSameType) {
super(objectPostProcessor, allowConfigurersOfSameType);
}
public StateMachineTransitionBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
@Override
protected StateMachineTransitions<S, E> performBuild() throws Exception {
StateMachineTransitions<S, E> bean = new StateMachineTransitions<S, E>(transitionData);
return bean;
}
@Override
public ExternalTransitionConfigurer<S, E> withExternal() throws Exception {
return apply(new DefaultExternalTransitionConfigurer<S, E>());
}
@Override
public InternalTransitionConfigurer<S, E> withInternal() throws Exception {
return apply(new DefaultInternalTransitionConfigurer<S, E>());
}
public void add(S source, S target, E event, Collection<Action> actions, Guard guard, TransitionKind kind) {
transitionData.add(new TransitionData<S, E>(source, target, event, actions, guard, kind));
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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.config.builders;
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
public interface StateMachineTransitionConfigurer<S, E> {
ExternalTransitionConfigurer<S, E> withExternal() throws Exception;
InternalTransitionConfigurer<S, E> withInternal() throws Exception;
}

View File

@@ -0,0 +1,71 @@
/*
* 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.config.builders;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.transition.TransitionKind;
public class StateMachineTransitions<S, E> {
private Collection<TransitionData<S, E>> transitions;
public StateMachineTransitions(Collection<TransitionData<S, E>> transitions) {
this.transitions = transitions;
}
public Collection<TransitionData<S, E>> getTransitions() {
return transitions;
}
public static class TransitionData<S, E> {
S source;
S target;
E event;
Collection<Action> actions;
Guard guard;
TransitionKind kind;
public TransitionData(S source, S target, E event, Collection<Action> actions, Guard guard, TransitionKind kind) {
this.source = source;
this.target = target;
this.event = event;
this.actions = actions;
this.guard = guard;
this.kind = kind;
}
public S getSource() {
return source;
}
public S getTarget() {
return target;
}
public E getEvent() {
return event;
}
public Collection<Action> getActions() {
return actions;
}
public Guard getGuard() {
return guard;
}
public TransitionKind getKind() {
return kind;
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.config.common.annotation;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A base {@link AnnotationBuilder} that ensures the object being built is only
* built one time.
*
* @author Rob Winch
* @author Janne Valkealahti
*
* @param <O> the type of Object that is being built
*/
public abstract class AbstractAnnotationBuilder<O> implements AnnotationBuilder<O> {
/** Flag tracking build */
private AtomicBoolean building = new AtomicBoolean();
/** Built object is stored here */
private O object;
@Override
public final O build() throws Exception {
if(building.compareAndSet(false, true)) {
object = doBuild();
return object;
}
throw new IllegalStateException("This object has already been built");
}
/**
* Gets the object that was built. If it has not been built yet an Exception
* is thrown.
*
* @return the Object that was built
*/
public final O getObject() {
if(!building.get()) {
throw new IllegalStateException("This object has not been built");
}
return object;
}
/**
* Subclasses should implement this to perform the build.
*
* @return the object that should be returned by {@link #build()}.
* @throws Exception if an error occurs
*/
protected abstract O doBuild() throws Exception;
}

View File

@@ -0,0 +1,96 @@
/*
* 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.config.common.annotation;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
* Base implementation of @{@link Configuration} class.
*
* @author Janne Valkealahti
*
* @param <O> The object that used builder returns
* @param <B> The type of the builder
*/
public abstract class AbstractAnnotationConfiguration<B extends AnnotationBuilder<O>, O>
implements ImportAware, BeanClassLoaderAware {
private List<AnnotationConfigurer<O,B>> configurers;
private ClassLoader beanClassLoader;
private AnnotationAttributes annotationAttributes;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
beanClassLoader = classLoader;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableConfigurationAttrMap =
importMetadata.getAnnotationAttributes(EnableAnnotationConfiguration.class.getName());
AnnotationAttributes enableConfigurationAttrs = AnnotationAttributes.fromMap(enableConfigurationAttrMap);
if(enableConfigurationAttrs == null) {
// search parent classes
Class<?> currentClass = ClassUtils.resolveClassName(importMetadata.getClassName(), beanClassLoader);
for(Class<?> classToInspect = currentClass ;classToInspect != null; classToInspect = classToInspect.getSuperclass()) {
EnableAnnotationConfiguration enableConfigurationAnnotation =
AnnotationUtils.findAnnotation(classToInspect, EnableAnnotationConfiguration.class);
if(enableConfigurationAnnotation == null) {
continue;
}
enableConfigurationAttrMap = AnnotationUtils
.getAnnotationAttributes(enableConfigurationAnnotation);
enableConfigurationAttrs = AnnotationAttributes.fromMap(enableConfigurationAttrMap);
}
}
annotationAttributes = enableConfigurationAttrs;
}
/**
* Sets the configurers.
*
* @param configurers the configurers
* @throws Exception the exception
*/
@Autowired(required=false)
public void setConfigurers(List<AnnotationConfigurer<O, B>> configurers) throws Exception {
this.configurers = configurers;
onConfigurers(configurers);
}
public AnnotationAttributes getAnnotationAttributes() {
return annotationAttributes;
}
public List<AnnotationConfigurer<O, B>> getConfigurers() {
return configurers;
}
protected abstract void onConfigurers(List<AnnotationConfigurer<O, B>> configurers) throws Exception;
}

View File

@@ -0,0 +1,531 @@
/*
* 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.config.common.annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
* A base {@link AnnotationBuilder} that allows {@link AnnotationConfigurer}s to be
* applied to it. This makes modifying the {@link AnnotationBuilder} a strategy
* that can be customised and broken up into a number of
* {@link AnnotationConfigurer} objects that have more specific goals than that
* of the {@link AnnotationBuilder}.
* <p>
*
*
* @author Rob Winch
* @author Janne Valkealahti
*
* @param <O> The object that this builder returns
* @param <I> The interface of type B
* @param <B> The type of this builder (that is returned by the base class)
*/
public abstract class AbstractConfiguredAnnotationBuilder<O,I,B extends AnnotationBuilder<O>>
extends AbstractAnnotationBuilder<O> {
private final static Log log = LogFactory.getLog(AbstractConfiguredAnnotationBuilder.class);
/** Configurers which are added to this builder before the configure step */
private final LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>> mainConfigurers =
new LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>>();
/** Configurers which are added to this builder during the configuration phase */
private final LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>> postConfigurers =
new LinkedHashMap<Class<? extends AnnotationConfigurer<O, B>>, List<AnnotationConfigurer<O, B>>>();
private final Map<Class<Object>, Object> sharedObjects = new HashMap<Class<Object>, Object>();
private final boolean allowConfigurersOfSameType;
/** Current state of this builder */
private BuildState buildState = BuildState.UNBUILT;
private ObjectPostProcessor<Object> objectPostProcessor;
/**
* Instantiates a new annotation builder.
*/
protected AbstractConfiguredAnnotationBuilder() {
this(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR);
}
/**
* Instantiates a new annotation builder.
*
* @param objectPostProcessor the object post processor
*/
protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
this(objectPostProcessor,false);
}
/**
* Instantiates a new annotation builder.
*
* @param objectPostProcessor the object post processor
* @param allowConfigurersOfSameType the allow configurers of same type
*/
protected AbstractConfiguredAnnotationBuilder(ObjectPostProcessor<Object> objectPostProcessor, boolean allowConfigurersOfSameType) {
Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
}
@Override
protected final O doBuild() throws Exception {
synchronized (mainConfigurers) {
buildState = BuildState.INITIALIZING_MAINS;
beforeInit();
initMainConfigurers();
buildState = BuildState.CONFIGURING_MAINS;
beforeConfigureMains();
configureMainConfigurers();
buildState = BuildState.CONFIGURING_POSTS;
beforeConfigurePosts();
configurePostConfigurers();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
/**
* Similar to {@link #build()} and {@link #getObject()} but checks the state
* to determine if {@link #build()} needs to be called first.
*
* @return the result of {@link #build()} or {@link #getObject()}. If an
* error occurs while building, returns null.
*/
public O getOrBuild() {
if (isUnbuilt()) {
try {
return build();
} catch (Exception e) {
log.error("Failed to perform build. Returning null", e);
return null;
}
} else {
return getObject();
}
}
/**
* Applies a {@link AnnotationConfigurerAdapter} to this
* {@link AnnotationBuilder} and invokes
* {@link AnnotationConfigurerAdapter#setBuilder(AnnotationBuilder)}.
*
* @param configurer the configurer
* @param <C> type of AnnotationConfigurer
* @return Configurer passed as parameter
* @throws Exception if error occurred
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurerAdapter<O,I,B>> C apply(C configurer) throws Exception {
add(configurer);
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
return configurer;
}
/**
* Similar to {@link #apply(AnnotationConfigurer)} but checks the state
* to determine if {@link #apply(AnnotationConfigurer)} needs to be called first.
*
* @param configurer the configurer
* @param <C> type of AnnotationConfigurer
* @return Configurer passed as parameter
* @throws Exception if error occurred
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurerAdapter<O,I,B>> C getOrApply(C configurer) throws Exception {
C existing = (C) getConfigurer(configurer.getClass());
if (existing != null) {
return existing;
}
return apply(configurer);
}
/**
* Applies a {@link AnnotationConfigurer} to this {@link AnnotationBuilder}
* overriding any {@link AnnotationConfigurer} of the exact same class. Note
* that object hierarchies are not considered.
*
* @param configurer the configurer
* @param <C> type of AnnotationConfigurer
* @return Configurer passed as parameter
* @throws Exception if error occurred
*/
public <C extends AnnotationConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
/**
* Sets an object that is shared by multiple {@link AnnotationConfigurer}.
*
* @param sharedType the Class to key the shared object by.
* @param object the Object to store
* @param <C> type of share object
*/
@SuppressWarnings("unchecked")
public <C> void setSharedObject(Class<C> sharedType, C object) {
this.sharedObjects.put((Class<Object>) sharedType, object);
}
/**
* Gets a shared Object. Note that object hierarchies are not considered.
*
* @param sharedType the type of the shared Object
* @param <C> type of share object
* @return the shared Object or null if it is not found
*/
@SuppressWarnings("unchecked")
public <C> C getSharedObject(Class<C> sharedType) {
return (C) this.sharedObjects.get(sharedType);
}
/**
* Gets the shared objects.
*
* @return Shared objects
*/
public Map<Class<Object>, Object> getSharedObjects() {
return Collections.unmodifiableMap(this.sharedObjects);
}
/**
* Adds {@link AnnotationConfigurer} ensuring that it is allowed and
* invoking {@link AnnotationConfigurer#init(AnnotationBuilder)} immediately
* if necessary.
*
* @param configurer the {@link AnnotationConfigurer} to add
* @param <C> type of AnnotationConfigurer
* @throws Exception if an error occurs
*/
@SuppressWarnings("unchecked")
private <C extends AnnotationConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends AnnotationConfigurer<O, B>> clazz =
(Class<? extends AnnotationConfigurer<O, B>>) configurer.getClass();
if (!buildState.isConfigured()) {
synchronized (mainConfigurers) {
List<AnnotationConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.mainConfigurers.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<AnnotationConfigurer<O, B>>(1);
}
configs.add(configurer);
this.mainConfigurers.put(clazz, configs);
if (buildState.isInitializing()) {
configurer.init((B) this);
}
}
} else {
synchronized (postConfigurers) {
List<AnnotationConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.postConfigurers.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<AnnotationConfigurer<O, B>>(1);
}
configs.add(configurer);
this.postConfigurers.put(clazz, configs);
configurer.init((B) this);
}
}
}
/**
* Gets all the {@link AnnotationConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link AnnotationConfigurer} class to look for
* @param <C> type of AnnotationConfigurer
* @return All configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) {
List<C> configs = (List<C>) this.mainConfigurers.get(clazz);
if (configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Removes all the {@link AnnotationConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link AnnotationConfigurer} class to look for
* @param <C> type of AnnotationConfigurer
* @return Empty list of configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> List<C> removeConfigurers(Class<C> clazz) {
List<C> configs = (List<C>) this.mainConfigurers.remove(clazz);
if (configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Gets the {@link AnnotationConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the configurer class type
* @param <C> type of AnnotationConfigurer
* @return Matched configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> C getConfigurer(Class<C> clazz) {
List<AnnotationConfigurer<O, B>> configs = this.mainConfigurers.get(clazz);
if (configs == null) {
return null;
}
if (configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Removes and returns the {@link AnnotationConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the configurer class type
* @param <C> type of AnnotationConfigurer
* @return Matched configurers
*/
@SuppressWarnings("unchecked")
public <C extends AnnotationConfigurer<O, B>> C removeConfigurer(Class<C> clazz) {
List<AnnotationConfigurer<O, B>> configs = this.mainConfigurers.remove(clazz);
if (configs == null) {
return null;
}
if (configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Specifies the {@link ObjectPostProcessor} to use.
* @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null
* @return the {@link AnnotationBuilder} for further customizations
*/
@SuppressWarnings("unchecked")
public O objectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
Assert.notNull(objectPostProcessor,"objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
return (O) this;
}
/**
* Performs post processing of an object. The default is to delegate to the
* {@link ObjectPostProcessor}.
*
* @param object the Object to post process
* @param <P> type of processed object
* @return the possibly modified Object to use
*/
protected <P> P postProcess(P object) {
return (P) this.objectPostProcessor.postProcess(object);
}
/**
* Invoked prior to invoking each
* {@link AnnotationConfigurer#init(AnnotationBuilder)} method. Subclasses may
* override this method to hook into the lifecycle without using a
* {@link AnnotationConfigurer}.
*
* @throws Exception if error occurred
*/
protected void beforeInit() throws Exception {
}
/**
* Invoked prior to invoking each main
* {@link AnnotationConfigurer#configure(AnnotationBuilder)} method.
* Subclasses may override this method to hook into the lifecycle without
* using a {@link AnnotationConfigurer}.
*
* @throws Exception if error occurred
*/
protected void beforeConfigureMains() throws Exception {
}
/**
* Invoked prior to invoking each post
* {@link AnnotationConfigurer#configure(AnnotationBuilder)} method.
* Subclasses may override this method to hook into the lifecycle without
* using a {@link AnnotationConfigurer}.
*
* @throws Exception if error occurred
*/
protected void beforeConfigurePosts() throws Exception {
}
/**
* Subclasses must implement this method to build the object that is being returned.
*
* @return Object build by this builder
* @throws Exception if error occurred
*/
protected abstract O performBuild() throws Exception;
@SuppressWarnings("unchecked")
private void initMainConfigurers() throws Exception {
for (AnnotationConfigurer<O, B> configurer : getMainConfigurers()) {
configurer.init((B) this);
}
}
@SuppressWarnings("unchecked")
private void configureMainConfigurers() throws Exception {
for (AnnotationConfigurer<O, B> configurer : getMainConfigurers()) {
configurer.configure((B) this);
}
}
@SuppressWarnings("unchecked")
private void configurePostConfigurers() throws Exception {
for (AnnotationConfigurer<O, B> configurer : getPostConfigurers()) {
configurer.configure((B) this);
}
}
/**
* Gets all configurers.
*
* @return the configurers
*/
private Collection<AnnotationConfigurer<O, B>> getMainConfigurers() {
List<AnnotationConfigurer<O, B>> result = new ArrayList<AnnotationConfigurer<O, B>>();
for (List<AnnotationConfigurer<O, B>> configs : this.mainConfigurers.values()) {
result.addAll(configs);
}
return result;
}
private Collection<AnnotationConfigurer<O, B>> getPostConfigurers() {
List<AnnotationConfigurer<O, B>> result = new ArrayList<AnnotationConfigurer<O, B>>();
for (List<AnnotationConfigurer<O, B>> configs : this.postConfigurers.values()) {
result.addAll(configs);
}
return result;
}
/**
* Determines if the object is unbuilt.
*
* @return true, if unbuilt else false
*/
private boolean isUnbuilt() {
synchronized (mainConfigurers) {
return buildState == BuildState.UNBUILT;
}
}
/**
* The build state for the application
*/
private static enum BuildState {
/**
* This is the state before the {@link AnnotationBuilder#build()} is invoked
*/
UNBUILT(0),
/**
* The state from when {@link AnnotationBuilder#build()} is first invoked until
* all the {@link AnnotationConfigurer#init(AnnotationBuilder)} methods have
* been invoked.
*/
INITIALIZING_MAINS(1),
/**
* The state from after all main
* {@link AnnotationConfigurer#init(AnnotationBuilder)}
* have been invoked until after all the
* {@link AnnotationConfigurer#configure(AnnotationBuilder)}
* methods have been invoked.
*/
CONFIGURING_MAINS(2),
/**
* The state from after all post
* {@link AnnotationConfigurer#init(AnnotationBuilder)}
* have been invoked until after all the
* {@link AnnotationConfigurer#configure(AnnotationBuilder)}
* methods have been invoked.
*/
CONFIGURING_POSTS(3),
/**
* From the point after all the
* {@link AnnotationConfigurer#configure(AnnotationBuilder)}
* have completed to just after
* {@link AbstractConfiguredAnnotationBuilder#performBuild()}.
*/
BUILDING(4),
/**
* After the object has been completely built.
*/
BUILT(5);
private final int order;
BuildState(int order) {
this.order = order;
}
/**
* Checks if is initializing.
*
* @return true, if is initializing
*/
public boolean isInitializing() {
return INITIALIZING_MAINS.order == order;
}
/**
* Determines if the state is CONFIGURING or later
*
* @return true, if configured
*/
public boolean isConfigured() {
return order >= CONFIGURING_MAINS.order;
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* 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.config.common.annotation;
import java.lang.annotation.Annotation;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Base class for {@link Configuration} which works on a bean definition level
* relying on {@link ImportBeanDefinitionRegistrar} phase to register beans.
*
* @author Janne Valkealahti
*
* @param <O> The object that used builder returns
* @param <B> The type of the builder
*/
public abstract class AbstractImportingAnnotationConfiguration<B extends AnnotationBuilder<O>, O> implements
ImportBeanDefinitionRegistrar, BeanFactoryAware, EnvironmentAware {
private BeanFactory beanFactory;
private Environment environment;
private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Class<?> annotationType = getAnnotation();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(
annotationType.getName(), false));
String[] names = attributes.getStringArray("name");
BeanDefinition beanDefinition;
try {
beanDefinition = buildBeanDefinition();
} catch (Exception e) {
throw new RuntimeException("Error with onConfigurers", e);
}
if (ObjectUtils.isEmpty(names)) {
// ok, name(s) not given, generate one
names = new String[] { beanNameGenerator.generateBeanName(beanDefinition, registry) };
}
registry.registerBeanDefinition(names[0], beanDefinition);
if (names.length > 1) {
for (int i = 1; i < names.length; i++) {
registry.registerAlias(names[0], names[i]);
}
}
}
protected abstract static class BeanDelegatingFactoryBean<B extends AnnotationBuilder<O>, O>
implements FactoryBean<O>, InitializingBean {
private final B builder;
private O object;
private List<AnnotationConfigurer<O, B>> configurers;
public BeanDelegatingFactoryBean(B builder){
this.builder = builder;
}
@Override
public abstract Class<O> getObjectType();
@Override
public O getObject() throws Exception {
return object;
}
@Override
public boolean isSingleton() {
return true;
}
// @Override
// public void afterPropertiesSet() throws Exception {
// for (AnnotationConfigurer<O, B> configurer : configurers) {
// if (configurer.isAssignable(builder)) {
// // we need builder.apply(configurer);
//// builder.
// }
// }
// // should be getOrBuild???
// object = builder.build();
// }
@Autowired(required = false)
public void setConfigurers(List<AnnotationConfigurer<O, B>> configurers) {
this.configurers = configurers;
}
public B getBuilder() {
return builder;
}
public List<AnnotationConfigurer<O, B>> getConfigurers() {
return configurers;
}
protected void setObject(O object) {
this.object = object;
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
"beanFactory be of type ListableBeanFactory but was " + beanFactory);
this.beanFactory = beanFactory;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* Called to get a bean definition to register.
*
* @return the bean definition to register
* @throws Exception if error occurred
*/
protected abstract BeanDefinition buildBeanDefinition() throws Exception;
/**
* Gets the annotation specific for this configurer.
*
* @return the annotation
*/
protected abstract Class<? extends Annotation> getAnnotation();
/**
* Gets the bean factory.
*
* @return the bean factory
*/
protected BeanFactory getBeanFactory() {
return beanFactory;
}
/**
* Gets the environment.
*
* @return the environment
*/
protected Environment getEnvironment() {
return environment;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.config.common.annotation;
/**
* Interface for building an {@link Object}.
*
* @author Rob Winch
* @author Janne Valkealahti
*
* @param <O> The type of the Object being built
*/
public interface AnnotationBuilder<O> {
/**
* Builds the object and returns it or null.
*
* @return the Object to be built or null if the implementation allows it.
* @throws Exception if an error occurred when building the Object
*/
O build() throws Exception;
}

View File

@@ -0,0 +1,58 @@
/*
* 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.config.common.annotation;
/**
* Allows for configuring an {@link AnnotationBuilder}. All
* {@link AnnotationConfigurer}s first have their {@link #init(AnnotationBuilder)}
* method invoked. After all {@link #init(AnnotationBuilder)} methods have been
* invoked, each {@link #configure(AnnotationBuilder)} method is invoked.
*
* @see AbstractConfiguredAnnotationBuilder
*
* @author Rob Winch
* @author Janne Valkealahti
*
* @param <O> The object being built by the {@link AnnotationBuilder} B
* @param <B> The {@link AnnotationBuilder} that builds objects of type O. This is
* also the {@link AnnotationBuilder} that is being configured.
*/
public interface AnnotationConfigurer<O, B extends AnnotationBuilder<O>> {
/**
* Initialise the {@link AnnotationBuilder}. Here only shared state should be
* created and modified, but not properties on the {@link AnnotationBuilder}
* used for building the object. This ensures that the
* {@link #configure(AnnotationBuilder)} method uses the correct shared
* objects when building.
*
* @param builder the builder
* @throws Exception if error occurred
*/
void init(B builder) throws Exception;
/**
* Configure the {@link AnnotationBuilder} by setting the necessary properties
* on the {@link AnnotationBuilder}.
*
* @param builder the builder
* @throws Exception if error occurred
*/
void configure(B builder) throws Exception;
boolean isAssignable(AnnotationBuilder<O> builder);
}

View File

@@ -0,0 +1,133 @@
/*
* 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.config.common.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.GenericTypeResolver;
/**
* A base class for {@link AnnotationConfigurer} that allows subclasses to only
* implement the methods they are interested in. It also provides a mechanism
* for using the {@link AnnotationConfigurer} and when done gaining access to the
* {@link AnnotationBuilder} that is being configured.
*
* @author Rob Winch
* @author Janne Valkealahti
*
* @param <O> The Object being built by B
* @param <I> The interface of type B
* @param <B> The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter}
*/
public abstract class AnnotationConfigurerAdapter<O,I,B extends AnnotationBuilder<O>>
implements AnnotationConfigurer<O,B> {
private B builder;
private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();
@Override
public void init(B builder) throws Exception {}
@Override
public void configure(B builder) throws Exception {}
/**
* Return the {@link AnnotationBuilder} when done using the
* {@link AnnotationConfigurer}. This is useful for method chaining.
*
* @return the {@link AnnotationBuilder}
*/
@SuppressWarnings("unchecked")
public I and() {
// we're either casting to itself or its interface
return (I) getBuilder();
}
/**
* Gets the {@link AnnotationBuilder}. Cannot be null.
*
* @return the {@link AnnotationBuilder}
* @throws IllegalStateException if AnnotationBuilder is null
*/
protected final B getBuilder() {
if(builder == null) {
throw new IllegalStateException("annotationBuilder cannot be null");
}
return builder;
}
/**
* Adds an {@link ObjectPostProcessor} to be used for this adapter. The
* default implementation does nothing to the object.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
*/
public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
}
/**
* Sets the {@link AnnotationBuilder} to be used. This is automatically set
* when using
* {@link AbstractConfiguredAnnotationBuilder#apply(AnnotationConfigurerAdapter)}
*
* @param builder the {@link AnnotationBuilder} to set
*/
public void setBuilder(B builder) {
this.builder = builder;
}
/**
* An {@link ObjectPostProcessor} that delegates work to numerous
* {@link ObjectPostProcessor} implementations.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
private List<ObjectPostProcessor<? extends Object>> postProcessors = new ArrayList<ObjectPostProcessor<?>>();
@Override
public Object postProcess(Object object) {
for(ObjectPostProcessor opp : postProcessors) {
Class<?> oppClass = opp.getClass();
Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass,ObjectPostProcessor.class);
if(oppType == null || oppType.isAssignableFrom(object.getClass())) {
object = opp.postProcess(object);
}
}
return object;
}
/**
* Adds an {@link ObjectPostProcessor} to use
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to add
* @return true if the {@link ObjectPostProcessor} was added, else false
*/
private boolean addObjectPostProcessor(ObjectPostProcessor<? extends Object> objectPostProcessor) {
return this.postProcessors.add(objectPostProcessor);
}
}
@Override
public boolean isAssignable(AnnotationBuilder<O> builder) {
return true;
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.config.common.annotation;
/**
* Interface for wrapping a return type from {@link AnnotationConfigurer}
* into {@link AnnotationBuilder}.
*
* @author Janne Valkealahti
*
* @param <I> The parent return type of the configurer.
*/
public interface AnnotationConfigurerBuilder<I> {
/**
* Get a parent {@link AnnotationBuilder} working
* with a {@link AnnotationConfigurer}.
* @return The parent {@link AnnotationBuilder}
*/
I and();
}

View File

@@ -0,0 +1,36 @@
/*
* 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.config.common.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;
/**
* Base annotation used in JavaConfig order to enable
* some base functionality.
*
* @author Janne Valkealahti
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface EnableAnnotationConfiguration {
}

View File

@@ -0,0 +1,53 @@
/*
* 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.config.common.annotation;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Allows initialization of Objects. Typically this is used to call the
* {@link Aware} methods, {@link InitializingBean#afterPropertiesSet()}, and
* ensure that {@link DisposableBean#destroy()} has been invoked.
*
* @param <T> the bound of the types of Objects this {@link ObjectPostProcessor} supports.
*
* @author Rob Winch
*/
public interface ObjectPostProcessor<T> {
/**
* Initialize the object possibly returning a modified instance that should
* be used instead.
*
* @param object the object to initialize
* @param <O> the type of a processed object
* @return the initialized version of the object
*/
<O extends T> O postProcess(O object);
/**
* A do nothing implementation of the {@link ObjectPostProcessor}
*/
ObjectPostProcessor<Object> QUIESCENT_POSTPROCESSOR = new ObjectPostProcessor<Object>() {
@Override
public <T> T postProcess(T object) {
return object;
}
};
}

View File

@@ -0,0 +1,118 @@
/*
* 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.config.common.annotation.configuration;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
import org.springframework.util.Assert;
/**
* Post processor handling lifecycle methods of
* POJOs created from builder/configurer.
*
* @author Janne Valkealahti
*
*/
final class AutowireBeanFactoryObjectPostProcessor implements ObjectPostProcessor<Object>, DisposableBean, SmartLifecycle {
private final static Log log = LogFactory.getLog(AutowireBeanFactoryObjectPostProcessor.class);
private final AutowireCapableBeanFactory autowireBeanFactory;
private final List<DisposableBean> disposableBeans = new ArrayList<DisposableBean>();
private final List<Lifecycle> lifecycleBeans = new ArrayList<Lifecycle>();
private boolean running;
/**
* Instantiates a new autowire bean factory object post processor.
*
* @param autowireBeanFactory the autowire bean factory
*/
public AutowireBeanFactoryObjectPostProcessor(AutowireCapableBeanFactory autowireBeanFactory) {
Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null");
this.autowireBeanFactory = autowireBeanFactory;
}
@SuppressWarnings("unchecked")
@Override
public <T> T postProcess(T object) {
T result = (T) autowireBeanFactory.initializeBean(object, null);
if(result instanceof DisposableBean) {
disposableBeans.add((DisposableBean) result);
}
if(result instanceof Lifecycle) {
lifecycleBeans.add((Lifecycle) result);
}
return result;
}
@Override
public void destroy() throws Exception {
for(DisposableBean disposable : disposableBeans) {
try {
disposable.destroy();
} catch(Exception error) {
log.error(error);
}
}
}
@Override
public void start() {
running = true;
for (Lifecycle bean : lifecycleBeans) {
bean.start();
}
}
@Override
public void stop() {
for (Lifecycle bean : lifecycleBeans) {
bean.stop();
}
running = false;
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return 0;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.config.common.annotation.configuration;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.common.annotation.EnableAnnotationConfiguration;
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
/**
* Spring {@link Configuration} that exports the default
* {@link ObjectPostProcessor}. This class is not intended to be imported
* manually rather it is imported automatically when using {@link EnableAnnotationConfiguration}
*
* @author Rob Winch
* @author Janne Valkealahti
*
* @see EnableAnnotationConfiguration
*/
@Configuration
public class ObjectPostProcessorConfiguration {
@Bean
public ObjectPostProcessor<Object> objectPostProcessor(AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.config.common.annotation.configurers;
import java.util.Map;
import java.util.Properties;
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
/**
* {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer AnnotationConfigurer}
* which knows how to handle configuring a {@link Properties}.
*
* @author Janne Valkealahti
*
* @param <O> The Object being built by B
* @param <I> The type of interface or builder itself returned by the configurer
* @param <B> The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter}
*/
public class DefaultPropertiesConfigurer<O,I,B extends AnnotationBuilder<O>>
extends AnnotationConfigurerAdapter<O,I,B> implements PropertiesConfigurer<I> {
private Properties properties = new Properties();
/**
* Adds a {@link Properties} to this builder.
*
* @param properties the properties
* @return the {@link PropertiesConfigurer} for chaining
*/
@Override
public PropertiesConfigurer<I> properties(Properties properties) {
if (properties != null) {
this.properties.putAll(properties);
}
return this;
}
@Override
public PropertiesConfigurer<I> properties(Map<String, String> properties) {
Properties props = new Properties();
if (properties != null) {
props.putAll(properties);
}
return properties(props);
}
/**
* Adds a property to this builder.
*
* @param key the key
* @param value the value
* @return the {@link PropertiesConfigurer} for chaining
*/
@Override
public PropertiesConfigurer<I> property(String key, String value) {
properties.put(key, value);
return this;
}
/**
* Gets the {@link Properties} configured for this builder.
*
* @return the properties
*/
public Properties getProperties() {
return properties;
}
@Override
public void configure(B builder) throws Exception {
if (!configureProperties(builder, properties)) {
if (builder instanceof PropertiesConfigurerAware) {
((PropertiesConfigurerAware)builder).configureProperties(properties);
}
}
}
/**
* Configure properties. If this implementation is extended,
* custom configure handling can be handled here.
*
* @param builder the builder
* @param properties the properties
* @return true, if properties configure is handled
*/
protected boolean configureProperties(B builder, Properties properties){
return false;
};
}

View File

@@ -0,0 +1,126 @@
/*
* 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.config.common.annotation.configurers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
/**
* {@link AnnotationConfigurer} which knows how to handle
* configuring a {@link Resource}s.
*
* @author Janne Valkealahti
*
* @param <O> The Object being built by B
* @param <B> The Builder that is building O and is configured by {@link AnnotationConfigurerAdapter}
* @param <I> The type of an interface of B
*/
public class DefaultResourceConfigurer<O,I,B extends AnnotationBuilder<O>>
extends AnnotationConfigurerAdapter<O,I,B> implements ResourceConfigurer<I> {
private Set<Resource> resources = new HashSet<Resource>();
private final DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
@Override
public void configure(B builder) throws Exception {
if (!configureResources(builder, resources)) {
if (builder instanceof ResourceConfigurerAware) {
((ResourceConfigurerAware)builder).configureResources(resources);
}
}
}
/**
* Adds a {@link Set} of {@link Resource}s to this builder.
*
* @param resources the resources
* @return the {@link ResourceConfigurer} for chaining
*/
@Override
public ResourceConfigurer<I> resources(Set<Resource> resources) {
this.resources.addAll(resources);
return this;
}
/**
* Adds a {@link Resource} to this builder.
*
* @param resource the resource
* @return the {@link ResourceConfigurer} for chaining
*/
@Override
public ResourceConfigurer<I> resource(Resource resource) {
resources.add(resource);
return this;
}
/**
* Adds a {@link Resource} to this builder.
*
* @param resource the resource
* @return the {@link ResourceConfigurer} for chaining
*/
@Override
public ResourceConfigurer<I> resource(String resource) {
resources.add(resourceLoader.getResource(resource));
return this;
}
/**
* Adds a {@link Resource}s to this builder.
*
* @param resources the resources
* @return the {@link ResourceConfigurer} for chaining
*/
@Override
public ResourceConfigurer<I> resources(List<String> resources) {
if (resources != null) {
for (String resource : resources) {
resource(resource);
}
}
return this;
}
/**
* Gets the {@link Resource}s configured for this builder.
*
* @return the resources
*/
public Set<Resource> getResources() {
return resources;
}
/**
* Configure resources. If this implementation is extended,
* custom configure handling can be handled here.
*
* @param builder the builder
* @param resources the resources
* @return true, if resources configure is handled
*/
protected boolean configureResources(B builder, Set<Resource> resources){
return false;
};
}

View File

@@ -0,0 +1,40 @@
/*
* 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.config.common.annotation.configurers;
import java.util.Map;
import java.util.Properties;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
/**
* Interface for {@link DefaultPropertiesConfigurer} which act
* as intermediate gatekeeper between a user and
* an {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer}.
*
* @author Janne Valkealahti
*
* @param <I> The parent return type of the configurer.
*/
public interface PropertiesConfigurer<I> extends AnnotationConfigurerBuilder<I> {
PropertiesConfigurer<I> properties(Properties properties);
PropertiesConfigurer<I> properties(Map<String, String> properties);
PropertiesConfigurer<I> property(String key, String value);
}

View File

@@ -0,0 +1,36 @@
/*
* 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.config.common.annotation.configurers;
import java.util.Properties;
/**
* Interface for {@link org.springframework.statemachine.config.common.annotation.AnnotationBuilder AnnotationBuilder}
* which wants to be aware of {@link Properties} configured by {@link DefaultPropertiesConfigurer}.
*
* @author Janne Valkealahti
*
*/
public interface PropertiesConfigurerAware {
/**
* Configure {@link Properties}.
*
* @param properties the properties
*/
void configureProperties(Properties properties);
}

View File

@@ -0,0 +1,43 @@
/*
* 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.config.common.annotation.configurers;
import java.util.List;
import java.util.Set;
import org.springframework.core.io.Resource;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
/**
* Interface for {@link DefaultResourceConfigurer} which act
* as intermediate gatekeeper between a user and
* an {@link org.springframework.statemachine.config.common.annotation.AnnotationConfigurer}.
*
* @author Janne Valkealahti
*
* @param <I> The parent return type of the configurer.
*/
public interface ResourceConfigurer<I> extends AnnotationConfigurerBuilder<I> {
ResourceConfigurer<I> resources(Set<Resource> resources);
ResourceConfigurer<I> resources(List<String> resources);
ResourceConfigurer<I> resource(Resource resource);
ResourceConfigurer<I> resource(String resource);
}

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.config.common.annotation.configurers;
import java.util.Set;
import org.springframework.core.io.Resource;
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
/**
* Interface for {@link AnnotationBuilder} which wants to be
* aware of {@link Resource}s configured by {@link DefaultResourceConfigurer}.
*
* @author Janne Valkealahti
*
*/
public interface ResourceConfigurerAware {
/**
* Configure {@link Resource}s.
*
* @param resources the resources
*/
void configureResources(Set<Resource> resources);
}

View File

@@ -0,0 +1,103 @@
package org.springframework.statemachine.config.configuration;
import java.lang.annotation.Annotation;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfig;
import org.springframework.statemachine.config.builders.StateMachineConfigBuilder;
import org.springframework.statemachine.config.builders.StateMachineStates;
import org.springframework.statemachine.config.builders.StateMachineTransitions;
import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer;
import org.springframework.statemachine.state.State;
@Configuration
public class StateMachineConfiguration<S extends Enum<S>, E extends Enum<E>> extends
AbstractImportingAnnotationConfiguration<StateMachineConfigBuilder<S, E>, StateMachineConfig<S, E>> {
private final StateMachineConfigBuilder<S, E> builder = new StateMachineConfigBuilder<S, E>();
@Override
protected BeanDefinition buildBeanDefinition() throws Exception {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(StateMachineDelegatingFactoryBean.class);
beanDefinitionBuilder.addConstructorArgValue(builder);
return beanDefinitionBuilder.getBeanDefinition();
}
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableStateMachine.class;
}
private static class StateMachineDelegatingFactoryBean<S extends Enum<S>, E extends Enum<E>> implements
FactoryBean<StateMachine<State<S, E>, E>>, BeanFactoryAware, InitializingBean {
private final StateMachineConfigBuilder<S, E> builder;
private List<AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>>> configurers;
private BeanFactory beanFactory;
private StateMachine<State<S, E>, E> stateMachine;
@SuppressWarnings("unused")
public StateMachineDelegatingFactoryBean(StateMachineConfigBuilder<S, E> builder) {
this.builder = builder;
}
@Override
public StateMachine<State<S, E>, E> getObject() throws Exception {
return stateMachine;
}
@Override
public Class<?> getObjectType() {
return StateMachine.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
for (AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>> configurer : configurers) {
builder.apply(configurer);
}
StateMachineConfig<S, E> stateMachineConfig = builder.getOrBuild();
StateMachineTransitions<S, E> stateMachineTransitions = stateMachineConfig.getTransitions();
StateMachineStates<S, E> stateMachineStates = stateMachineConfig.getStates();
EnumStateMachineFactory<S,E> stateMachineFactory = new EnumStateMachineFactory<S, E>(stateMachineTransitions, stateMachineStates);
stateMachineFactory.setBeanFactory(beanFactory);
stateMachine = stateMachineFactory.getStateMachine();
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Autowired(required=false)
protected void onConfigurers(
List<AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>>> configurers)
throws Exception {
this.configurers = configurers;
}
}
}

View File

@@ -0,0 +1,103 @@
package org.springframework.statemachine.config.configuration;
import java.lang.annotation.Annotation;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.EnumStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfig;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineConfigBuilder;
import org.springframework.statemachine.config.builders.StateMachineStates;
import org.springframework.statemachine.config.builders.StateMachineTransitions;
import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer;
import org.springframework.statemachine.state.State;
@Configuration
public class StateMachineFactoryConfiguration<S extends Enum<S>, E extends Enum<E>> extends
AbstractImportingAnnotationConfiguration<StateMachineConfigBuilder<S, E>, StateMachineConfig<S, E>> {
private final StateMachineConfigBuilder<S, E> builder = new StateMachineConfigBuilder<S, E>();
@Override
protected BeanDefinition buildBeanDefinition() throws Exception {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(StateMachineFactoryDelegatingFactoryBean.class);
beanDefinitionBuilder.addConstructorArgValue(builder);
return beanDefinitionBuilder.getBeanDefinition();
}
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableStateMachineFactory.class;
}
private static class StateMachineFactoryDelegatingFactoryBean<S extends Enum<S>, E extends Enum<E>> implements
FactoryBean<StateMachineFactory<State<S, E>, E>>, BeanFactoryAware, InitializingBean {
private final StateMachineConfigBuilder<S, E> builder;
private List<AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>>> configurers;
private BeanFactory beanFactory;
private StateMachineFactory<State<S, E>, E> stateMachineFactory;
@SuppressWarnings("unused")
public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder<S, E> builder) {
this.builder = builder;
}
@Override
public StateMachineFactory<State<S, E>, E> getObject() throws Exception {
return stateMachineFactory;
}
@Override
public Class<?> getObjectType() {
return StateMachineFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
for (AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>> configurer : configurers) {
builder.apply(configurer);
}
StateMachineConfig<S, E> stateMachineConfig = builder.getOrBuild();
StateMachineTransitions<S, E> stateMachineTransitions = stateMachineConfig.getTransitions();
StateMachineStates<S, E> stateMachineStates = stateMachineConfig.getStates();
EnumStateMachineFactory<S,E> enumStateMachineFactory = new EnumStateMachineFactory<S, E>(stateMachineTransitions, stateMachineStates);
enumStateMachineFactory.setBeanFactory(beanFactory);
this.stateMachineFactory = enumStateMachineFactory;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Autowired(required=false)
protected void onConfigurers(
List<AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>>> configurers)
throws Exception {
this.configurers = configurers;
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.config.configurers;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitions;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.guard.SpelExpressionGuard;
import org.springframework.statemachine.transition.TransitionKind;
/**
* Default implementation of a {@link ExternalTransitionConfigurer}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class DefaultExternalTransitionConfigurer<S, E>
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
implements ExternalTransitionConfigurer<S, E> {
private S source;
private S target;
private E event;
private Collection<Action> actions = new ArrayList<Action>();
private Guard guard;
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.add(source, target, event, actions, guard, TransitionKind.EXTERNAL);
}
@Override
public ExternalTransitionConfigurer<S, E> source(S source) {
this.source = source;
return this;
}
@Override
public ExternalTransitionConfigurer<S, E> target(S target) {
this.target = target;
return this;
}
@Override
public ExternalTransitionConfigurer<S, E> event(E event) {
this.event = event;
return this;
}
@Override
public ExternalTransitionConfigurer<S, E> action(Action action) {
actions.add(action);
return this;
}
@Override
public ExternalTransitionConfigurer<S, E> guard(Guard guard) {
this.guard = guard;
return this;
}
@Override
public ExternalTransitionConfigurer<S, E> guardExpression(String expression) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
this.guard = new SpelExpressionGuard(parser.parseExpression(expression));
return this;
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.config.configurers;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitions;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.guard.SpelExpressionGuard;
import org.springframework.statemachine.transition.TransitionKind;
/**
* Default implementation of a {@link InternalTransitionConfigurer}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class DefaultInternalTransitionConfigurer<S, E>
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
implements InternalTransitionConfigurer<S, E> {
private S source;
private S target;
private E event;
private Collection<Action> actions = new ArrayList<Action>();
private Guard guard;
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.add(source, target, event, actions, guard, TransitionKind.INTERNAL);
}
@Override
public InternalTransitionConfigurer<S, E> source(S source) {
this.source = source;
return this;
}
@Override
public InternalTransitionConfigurer<S, E> event(E event) {
this.event = event;
return this;
}
@Override
public InternalTransitionConfigurer<S, E> action(Action action) {
actions.add(action);
return this;
}
@Override
public InternalTransitionConfigurer<S, E> guard(Guard guard) {
this.guard = guard;
return this;
}
@Override
public InternalTransitionConfigurer<S, E> guardExpression(String expression) {
SpelExpressionParser parser = new SpelExpressionParser(
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
this.guard = new SpelExpressionGuard(parser.parseExpression(expression));
return this;
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.config.configurers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.builders.StateMachineStateBuilder;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStates;
import org.springframework.statemachine.config.builders.StateMachineStates.StateData;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
public class DefaultStateConfigurer<S, E>
extends AnnotationConfigurerAdapter<StateMachineStates<S, E>, StateMachineStateConfigurer<S, E>, StateMachineStateBuilder<S, E>>
implements StateConfigurer<S, E> {
private final Collection<StateData<S, E>> states = new ArrayList<StateData<S, E>>();
private S initial;
@Override
public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
builder.add(states);
builder.setInitialState(initial);
}
@Override
public StateConfigurer<S, E> initial(S initial) {
this.initial = initial;
return this;
}
@Override
public StateConfigurer<S, E> state(S state) {
return state(state, (E[])null);
}
@Override
public StateConfigurer<S, E> state(S state, Collection<Action> entryActions, Collection<Action> exitActions) {
states.add(new StateData<S, E>(state, null, entryActions, exitActions));
return this;
}
@Override
public StateConfigurer<S, E> state(S state, E... deferred) {
states.add(new StateData<S, E>(state, deferred));
return this;
}
@Override
public StateConfigurer<S, E> states(Set<S> states) {
for (S s : states) {
state(s);
}
return this;
}
}

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.config.configurers;
import org.springframework.statemachine.transition.Transition;
/**
* {@code TransitionConfigurer} interface for configuring external {@link Transition}s.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface ExternalTransitionConfigurer<S, E> extends
TransitionConfigurer<ExternalTransitionConfigurer<S, E>, S, E> {
/**
* Specify a target state {@code S} for this {@link Transition}.
*
* @param target the target state {@code S}
* @return configurer for chaining
*/
ExternalTransitionConfigurer<S, E> target(S target);
}

View File

@@ -0,0 +1,31 @@
/*
* 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.config.configurers;
import org.springframework.statemachine.transition.Transition;
/**
* {@code TransitionConfigurer} interface for configuring internal {@link Transition}s.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface InternalTransitionConfigurer<S, E> extends
TransitionConfigurer<InternalTransitionConfigurer<S, E>, S, E> {
}

View File

@@ -0,0 +1,38 @@
/*
* 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.config.configurers;
import java.util.Collection;
import java.util.Set;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
public interface StateConfigurer<S, E> extends
AnnotationConfigurerBuilder<StateMachineStateConfigurer<S, E>> {
StateConfigurer<S, E> initial(S initial);
StateConfigurer<S, E> state(S state);
StateConfigurer<S, E> state(S state, Collection<Action> entryActions, Collection<Action> exitActions);
StateConfigurer<S, E> state(S state, E... deferred);
StateConfigurer<S, E> states(Set<S> states);
}

View File

@@ -0,0 +1,76 @@
/*
* 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.config.configurers;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.transition.Transition;
/**
* Base {@code TransitionConfigurer} interface for configuring {@link Transition}s.
*
* @author Janne Valkealahti
*
* @param <T> the type of a transition configurer
* @param <S> the type of state
* @param <E> the type of event
*/
public interface TransitionConfigurer<T, S, E> extends
AnnotationConfigurerBuilder<StateMachineTransitionConfigurer<S, E>> {
/**
* Specify a source state {@code S} for this {@link Transition}.
*
* @param source the source state {@code S}
* @return configurer for chaining
*/
T source(S source);
/**
* Specify event {@code E} for this {@link Transition}.
*
* @param event the event for transition
* @return configurer for chaining
*/
T event(E event);
/**
* Specify {@link Action} for this {@link Transition}.
*
* @param action the action
* @return configurer for chaining
*/
T action(Action action);
/**
* Specify a {@link Guard} for this {@link Transition}.
*
* @param guard the guard
* @return configurer for chaining
*/
T guard(Guard guard);
/**
* Specify a {@link Guard} backed by a SpEL expression for this {@link Transition}.
*
* @param expression the SpEL expression
* @return configurer for chaining
*/
T guardExpression(String expression);
}

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.guard;
import org.springframework.statemachine.StateContext;
/**
* {@code Guard}s are typically considered as guard conditions which affect the
* behaviour of a state machine by enabling actions or transitions only when they
* evaluate to {@code TRUE} and disabling them when they evaluate to
* {@code FALSE}.
*
* @author Janne Valkealahti
*
*/
public interface Guard {
/**
* Evaluate a guard condition.
*
* @param context the state context
* @return true, if guard evaluation is successful, false otherwise.
*/
boolean evaluate(StateContext context);
}

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.guard;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.statemachine.StateContext;
import org.springframework.util.Assert;
/**
* {@link Guard} which uses Spring SpEL expression for condition evaluation.
*
* @author Janne Valkealahti
*
*/
public class SpelExpressionGuard implements Guard {
private final Expression expression;
/**
* Instantiates a new spel expression guard.
*
* @param expression the expression
*/
public SpelExpressionGuard(Expression expression) {
Assert.notNull(expression, "Expression cannot be null");
this.expression = expression;
}
@Override
public boolean evaluate(StateContext context) {
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(context);
return expression.getValue(evaluationContext, Boolean.class);
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.listener;
import java.util.List;
/**
* Base implementation for all composite listeners.
*
* @author Janne Valkealahti
*
* @param <T> the type of the listener
*/
public class AbstractCompositeListener<T> {
/** List of ordered composite listeners */
private OrderedComposite<T> listeners;
/**
* Constructs instance with an empty listener list.
*/
public AbstractCompositeListener() {
listeners = new OrderedComposite<T>();
}
/**
* Sets the list of listeners. This clears
* all existing listeners.
*
* @param listeners the new listeners
*/
public void setListeners(List<? extends T> listeners) {
this.listeners.setItems(listeners);
}
/**
* Register a new listener.
*
* @param listener the listener
*/
public void register(T listener) {
listeners.add(listener);
}
/**
* Gets the listeners.
*
* @return the listeners
*/
public OrderedComposite<T> getListeners() {
return listeners;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.listener;
import java.util.Iterator;
import org.springframework.statemachine.state.State;
public class CompositeStateMachineListener<S,E> extends AbstractCompositeListener<StateMachineListener<State<S,E>, E>> implements
StateMachineListener<State<S,E>, E> {
@Override
public void stateChanged(State<S, E> from, State<S, E> to) {
for (Iterator<StateMachineListener<State<S,E>, E>> iterator = getListeners().reverse(); iterator.hasNext();) {
StateMachineListener<State<S,E>, E> listener = iterator.next();
listener.stateChanged(from, to);
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.listener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
/**
* Composite item which can be used in other components which
* may want to allow automatic and annotation based ordering.
* Good use case is a list of listeners where user may want
* to place some of them to be processed before the others.
*
* @author Janne Valkealahti
*
* @param <S> the type of the item
*/
public class OrderedComposite<S> {
private List<S> unordered = new ArrayList<S>();
private List<S> ordered = new ArrayList<S>();
private Comparator<? super S> comparator = new AnnotationAwareOrderComparator();
private List<S> list = new ArrayList<S>();
/**
* Public setter for the listeners.
*
* @param items items
*/
public void setItems(List<? extends S> items) {
unordered.clear();
ordered.clear();
for (S s : items) {
add(s);
}
}
/**
* Register additional item.
*
* @param item item
*/
public void add(S item) {
if (item instanceof Ordered) {
if (!ordered.contains(item)) {
ordered.add(item);
}
} else if (AnnotationUtils.isAnnotationDeclaredLocally(Order.class, item.getClass())) {
if (!ordered.contains(item)) {
ordered.add(item);
}
} else if (!unordered.contains(item)) {
unordered.add(item);
}
Collections.sort(ordered, comparator);
list.clear();
list.addAll(ordered);
list.addAll(unordered);
}
/**
* Public getter for the list of items. The {@link Ordered} items come
* first, followed by any unordered ones.
*
* @return an iterator over the list of items
*/
public Iterator<S> iterator() {
return new ArrayList<S>(list).iterator();
}
/**
* Public getter for the list of items in reverse. The {@link Ordered} items
* come last, after any unordered ones.
*
* @return an iterator over the list of items
*/
public Iterator<S> reverse() {
ArrayList<S> result = new ArrayList<S>(list);
Collections.reverse(result);
return result.iterator();
}
}

View File

@@ -0,0 +1,22 @@
/*
* 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.listener;
public interface StateMachineListener<S,E> {
void stateChanged(S from, S to);
}

View File

@@ -0,0 +1,45 @@
/*
* 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;
/**
* Strategy interface for post-processing annotated methods.
*
* @author Mark Fisher
* @author Janne Valkealahti
*
* @param <T> the type of an annotation
*/
public interface MethodAnnotationPostProcessor<T extends Annotation> {
/**
* Post process a bean. As a result of a given bean, its name, method and
* annotation in a method, this method can return a new bean or
* <code>null</code>. Caller of this method is then responsible to handle
* newly created object.
*
* @param bean the bean
* @param beanName the bean name
* @param method the method
* @param annotation the annotation
* @return the object
*/
Object postProcess(Object bean, String beanName, Method method, T annotation);
}

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.processor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* A simple {@link StateMachineRuntimeProcessor} implementation using
* methods from a state machine protected bean.
*
* @author Janne Valkealahti
*
* @param <T> the return type
*/
public class MethodInvokingStateMachineRuntimeProcessor<T> implements StateMachineRuntimeProcessor<T> {
private final StateMachineMethodInvokerHelper<T> delegate;
public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Method method) {
delegate = new StateMachineMethodInvokerHelper<T>(targetObject, method);
}
public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, String methodName) {
delegate = new StateMachineMethodInvokerHelper<T>(targetObject, methodName);
}
public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Class<? extends Annotation> annotationType) {
delegate = new StateMachineMethodInvokerHelper<T>(targetObject, annotationType);
}
@Override
public T process(StateMachineRuntime stateMachineRuntime) {
try {
return delegate.process(stateMachineRuntime);
} catch (Exception e) {
throw new RuntimeException("Error processing bean", e);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
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>{
protected final BeanFactory beanFactory;
public StateMachineActivatorAnnotationPostProcessor(ListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcess(Object bean, String beanName, Method method, OnTransition annotation) {
StateMachineHandler handler = new StateMachineOnTransitionHandler(bean, method, annotation);
Integer order = findOrder(bean, method);
if (order != null) {
handler.setOrder(order);
}
return handler;
}
/**
* Find {@link Order} order either from a class or
* method level. Method level always takes presence
* over class level.
*
* @param bean the bean to inspect
* @param method the method to inspect
* @return the order or NULL if not found
*/
private static Integer findOrder(Object bean, Method method) {
Integer order = OrderUtils.getOrder(bean.getClass());
Order ann = AnnotationUtils.findAnnotation(method, Order.class);
if (ann != null) {
order = ann.value();
}
return order;
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationEvent;
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.OnTransition;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* A {@link BeanPostProcessor} implementation that processes method-level
* annotations such as {@link OnTransition}.
*
* @author Mark Fisher
* @author Marius Bogoevici
* @author Janne Valkealahti
*
*/
public class StateMachineAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean,
Lifecycle, ApplicationListener<ApplicationEvent> {
private final static Log log = LogFactory.getLog(StateMachineAnnotationPostProcessor.class);
/** Factory from BeanFactoryAware */
private volatile ConfigurableListableBeanFactory beanFactory;
/** Post processors map - annotation -> method post processor */
private final Map<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>> postProcessors =
new HashMap<Class<? extends Annotation>, MethodAnnotationPostProcessor<?>>();
/**
* Application events for post processed beans (if bean instance of ApplicationListener) will
* be dispatched from here via callback in this class.
*/
private final Set<ApplicationListener<ApplicationEvent>> listeners = new HashSet<ApplicationListener<ApplicationEvent>>();
/**
* Lifecycle callbacks for post processed bean (if bean instance of Lifecycle) will
* be dispatched from here via callback in this class.
*/
private final Set<Lifecycle> lifecycles = new HashSet<Lifecycle>();
/** Flag for Lifecycle in this class */
private volatile boolean running = true;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(),
"a ConfigurableListableBeanFactory is required");
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
@Override
public void afterPropertiesSet() {
Assert.notNull(beanFactory, "BeanFactory must not be null");
postProcessors.put(OnTransition.class, new StateMachineActivatorAnnotationPostProcessor(beanFactory));
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
Assert.notNull(beanFactory, "BeanFactory must not be null");
final Class<?> beanClass = getBeanClass(bean);
if (!isStereotype(beanClass)) {
// we only post-process stereotype components
return bean;
}
ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
@SuppressWarnings({ "unchecked", "rawtypes" })
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
for (Annotation annotation : annotations) {
MethodAnnotationPostProcessor postProcessor = postProcessors.get(annotation.annotationType());
if (postProcessor != null && shouldCreateHandler(annotation)) {
Object result = postProcessor.postProcess(bean, beanName, method, 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 Lifecycle) {
lifecycles.add((Lifecycle) result);
if (result instanceof SmartLifecycle && ((SmartLifecycle) result).isAutoStartup()) {
((SmartLifecycle) result).start();
}
}
if (result instanceof ApplicationListener) {
listeners.add((ApplicationListener) result);
}
}
}
}
}
});
return bean;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
for (ApplicationListener<ApplicationEvent> listener : listeners) {
try {
listener.onApplicationEvent(event);
} catch (ClassCastException e) {
if (log.isWarnEnabled() && event != null) {
log.warn("ApplicationEvent of type [" + event.getClass()
+ "] not accepted by ApplicationListener [" + listener + "]");
}
}
}
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public void start() {
for (Lifecycle lifecycle : this.lifecycles) {
if (!lifecycle.isRunning()) {
lifecycle.start();
}
}
this.running = true;
}
@Override
public void stop() {
for (Lifecycle lifecycle : this.lifecycles) {
if (lifecycle.isRunning()) {
lifecycle.stop();
}
}
this.running = false;
}
private boolean shouldCreateHandler(Annotation annotation) {
return true;
}
/**
* Gets the bean class. Will check if bean is a proxy and
* find a class from there as target class, otherwise
* we just get bean class.
*
* @param bean the bean
* @return the bean class
*/
private Class<?> getBeanClass(Object bean) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
return (targetClass != null) ? targetClass : bean.getClass();
}
/**
* Checks if class is a stereotype meaning if there is
* a Component annotation present.
*
* @param beanClass the bean class
* @return true, if is stereotype
*/
private boolean isStereotype(Class<?> beanClass) {
List<Annotation> annotations = new ArrayList<Annotation>(Arrays.asList(beanClass.getAnnotations()));
Class<?>[] interfaces = beanClass.getInterfaces();
for (Class<?> iface : interfaces) {
annotations.addAll(Arrays.asList(iface.getAnnotations()));
}
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType.equals(Component.class) || annotationType.isAnnotationPresent(Component.class)) {
return true;
}
}
return false;
}
private String generateBeanName(String originalBeanName, Method method, Class<? extends Annotation> annotationType) {
String baseName = originalBeanName + "." + method.getName() + "." + ClassUtils.getShortNameAsProperty(annotationType);
String name = baseName;
int count = 1;
while (beanFactory.containsBean(name)) {
name = baseName + "#" + (++count);
}
return name;
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.reflect.Method;
import org.springframework.core.Ordered;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
/**
* Handler for a common object representing something to be run.
* This is usually used when a plain pojo is configured with {@link WithStateMachine}
* and {@link OnTransition} annotations.
*
* @author Janne Valkealahti
*
*/
public class StateMachineHandler implements Ordered {
private final StateMachineRuntimeProcessor<?> processor;
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* Instantiates a new container handler.
*
* @param target the target bean
*/
// public StateMachineHandler(Object target) {
// this(new MethodInvokingStateMachineRuntimeProcessor<Object>(target, OnTransition.class));
// }
/**
* Instantiates a new container handler.
*
* @param target the target bean
* @param method the method
*/
public StateMachineHandler(Object target, Method method) {
this(new MethodInvokingStateMachineRuntimeProcessor<Object>(target, method));
}
/**
* Instantiates a new container handler.
*
* @param target the target bean
* @param methodName the method name
*/
public StateMachineHandler(Object target, String methodName) {
this(new MethodInvokingStateMachineRuntimeProcessor<Object>(target, methodName));
}
/**
* Instantiates a new container handler.
*
* @param <T> the generic type
* @param processor the processor
*/
public <T> StateMachineHandler(MethodInvokingStateMachineRuntimeProcessor<T> processor) {
this.processor = processor;
}
@Override
public int getOrder() {
return order;
}
/**
* Sets the order used get value from {@link #getOrder()}.
* Default value is {@link Ordered#LOWEST_PRECEDENCE}.
*
* @param order the new order
*/
public void setOrder(int order) {
this.order = order;
}
/**
* Handle container using a {@link StateMachineRuntimeProcessor}.
*
* @param stateMachineRuntime the state machine runtime
* @return the result value
*/
public Object handle(StateMachineRuntime stateMachineRuntime) {
return processor.process(stateMachineRuntime);
}
}

View File

@@ -0,0 +1,553 @@
/*
* 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 java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.support.AbstractExpressionEvaluator;
import org.springframework.statemachine.support.AnnotatedMethodFilter;
import org.springframework.statemachine.support.FixedMethodFilter;
import org.springframework.statemachine.support.UniqueMethodFilter;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* A helper class using spel to execute target methods.
*
* @author Janne Valkealahti
*
* @param <T> the return type
*/
public class StateMachineMethodInvokerHelper<T> extends AbstractExpressionEvaluator {
private static final String CANDIDATE_METHODS = "CANDIDATE_METHODS";
private static final String CANDIDATE_MESSAGE_METHODS = "CANDIDATE_MESSAGE_METHODS";
private final Log logger = LogFactory.getLog(this.getClass());
private final Object targetObject;
private volatile String displayString;
private volatile boolean requiresReply;
private final Map<Class<?>, HandlerMethod> handlerMethods;
private final Map<Class<?>, HandlerMethod> handlerMessageMethods;
private final LinkedList<Map<Class<?>, HandlerMethod>> handlerMethodsList;
private final HandlerMethod handlerMethod;
private final Class<?> expectedType;
public StateMachineMethodInvokerHelper(Object targetObject, Method method) {
this(targetObject, method, null);
}
public StateMachineMethodInvokerHelper(Object targetObject, Method method, Class<?> expectedType) {
this(targetObject, null, method, expectedType);
}
public StateMachineMethodInvokerHelper(Object targetObject, String methodName) {
this(targetObject, methodName, null);
}
public StateMachineMethodInvokerHelper(Object targetObject, String methodName, Class<?> expectedType) {
this(targetObject, null, methodName, expectedType);
}
public StateMachineMethodInvokerHelper(Object targetObject, Class<? extends Annotation> annotationType) {
this(targetObject, annotationType, null);
}
public StateMachineMethodInvokerHelper(Object targetObject, Class<? extends Annotation> annotationType,
Class<?> expectedType) {
this(targetObject, annotationType, (String) null, expectedType);
}
public T process(StateMachineRuntime stateMachineRuntime) throws Exception {
ParametersWrapper wrapper = new ParametersWrapper(stateMachineRuntime.getStateContext());
return processInternal(wrapper);
}
@Override
public String toString() {
return this.displayString;
}
private StateMachineMethodInvokerHelper(Object targetObject, Class<? extends Annotation> annotationType,
Method method, Class<?> expectedType) {
Assert.notNull(method, "method must not be null");
this.expectedType = expectedType;
this.requiresReply = expectedType != null;
if (expectedType != null) {
Assert.isTrue(method.getReturnType() != Void.class && method.getReturnType() != Void.TYPE,
"method must have a return type");
}
Assert.notNull(targetObject, "targetObject must not be null");
this.targetObject = targetObject;
this.handlerMethod = new HandlerMethod(method);
this.handlerMethods = null;
this.handlerMessageMethods = null;
this.handlerMethodsList = null;
this.prepareEvaluationContext(this.getEvaluationContext(false), method, annotationType);
this.setDisplayString(targetObject, method);
}
private StateMachineMethodInvokerHelper(Object targetObject, Class<? extends Annotation> annotationType,
String methodName, Class<?> expectedType) {
Assert.notNull(targetObject, "targetObject must not be null");
this.expectedType = expectedType;
this.targetObject = targetObject;
this.requiresReply = expectedType != null;
Map<String, Map<Class<?>, HandlerMethod>> handlerMethodsForTarget = this.findHandlerMethodsForTarget(
targetObject, annotationType, methodName, requiresReply);
Map<Class<?>, HandlerMethod> handlerMethods = handlerMethodsForTarget.get(CANDIDATE_METHODS);
Map<Class<?>, HandlerMethod> handlerMessageMethods = handlerMethodsForTarget.get(CANDIDATE_MESSAGE_METHODS);
if ((handlerMethods.size() == 1 && handlerMessageMethods.isEmpty())
|| (handlerMessageMethods.size() == 1 && handlerMethods.isEmpty())) {
if (handlerMethods.size() == 1) {
this.handlerMethod = handlerMethods.values().iterator().next();
} else {
this.handlerMethod = handlerMessageMethods.values().iterator().next();
}
this.handlerMethods = null;
this.handlerMessageMethods = null;
this.handlerMethodsList = null;
} else {
this.handlerMethod = null;
this.handlerMethods = handlerMethods;
this.handlerMessageMethods = handlerMessageMethods;
this.handlerMethodsList = new LinkedList<Map<Class<?>, HandlerMethod>>();
// TODO Consider to use global option to determine a precedence of
// methods
this.handlerMethodsList.add(this.handlerMethods);
this.handlerMethodsList.add(this.handlerMessageMethods);
}
this.prepareEvaluationContext(this.getEvaluationContext(false), methodName, annotationType);
this.setDisplayString(targetObject, methodName);
}
private void setDisplayString(Object targetObject, Object targetMethod) {
StringBuilder sb = new StringBuilder(targetObject.getClass().getName());
if (targetMethod instanceof Method) {
sb.append("." + ((Method) targetMethod).getName());
} else if (targetMethod instanceof String) {
sb.append("." + targetMethod);
}
this.displayString = sb.toString() + "]";
}
private void prepareEvaluationContext(StandardEvaluationContext context, Object method,
Class<? extends Annotation> annotationType) {
Class<?> targetType = AopUtils.getTargetClass(this.targetObject);
if (method instanceof Method) {
context.registerMethodFilter(targetType, new FixedMethodFilter((Method) method));
if (expectedType != null) {
Assert.state(
context.getTypeConverter().canConvert(
TypeDescriptor.valueOf(((Method) method).getReturnType()),
TypeDescriptor.valueOf(expectedType)), "Cannot convert to expected type ("
+ expectedType + ") from " + method);
}
} else if (method == null || method instanceof String) {
AnnotatedMethodFilter filter = new AnnotatedMethodFilter(annotationType, (String) method,
this.requiresReply);
Assert.state(canReturnExpectedType(filter, targetType, context.getTypeConverter()),
"Cannot convert to expected type (" + expectedType + ") from " + method);
context.registerMethodFilter(targetType, filter);
}
context.setVariable("target", targetObject);
}
private boolean canReturnExpectedType(AnnotatedMethodFilter filter, Class<?> targetType, TypeConverter typeConverter) {
if (expectedType == null) {
return true;
}
List<Method> methods = filter.filter(Arrays.asList(ReflectionUtils.getAllDeclaredMethods(targetType)));
for (Method method : methods) {
if (typeConverter.canConvert(TypeDescriptor.valueOf(method.getReturnType()), TypeDescriptor.valueOf(expectedType))) {
return true;
}
}
return false;
}
private T processInternal(ParametersWrapper parameters) throws Exception {
HandlerMethod candidate = this.findHandlerMethodForParameters(parameters);
Assert.notNull(candidate, "No candidate methods found for messages.");
Expression expression = candidate.getExpression();
Class<?> expectedType = this.expectedType != null ? this.expectedType : candidate.method.getReturnType();
try {
@SuppressWarnings("unchecked")
T result = (T) this.evaluateExpression(expression, parameters, expectedType);
if (this.requiresReply) {
Assert.notNull(result, "Expression evaluation result was null, but this processor requires a reply.");
}
return result;
} catch (Exception e) {
Throwable evaluationException = e;
if (e instanceof EvaluationException && e.getCause() != null) {
evaluationException = e.getCause();
}
if (evaluationException instanceof Exception) {
throw (Exception) evaluationException;
} else {
throw new IllegalStateException("Cannot process message", evaluationException);
}
}
}
private Map<String, Map<Class<?>, HandlerMethod>> findHandlerMethodsForTarget(final Object targetObject,
final Class<? extends Annotation> annotationType, final String methodName, final boolean requiresReply) {
Map<String, Map<Class<?>, HandlerMethod>> handlerMethods = new HashMap<String, Map<Class<?>, HandlerMethod>>();
final Map<Class<?>, HandlerMethod> candidateMethods = new HashMap<Class<?>, HandlerMethod>();
final Map<Class<?>, HandlerMethod> candidateMessageMethods = new HashMap<Class<?>, HandlerMethod>();
final Class<?> targetClass = this.getTargetClass(targetObject);
MethodFilter methodFilter = new UniqueMethodFilter(targetClass);
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
boolean matchesAnnotation = false;
if (method.isBridge()) {
return;
}
if (isMethodDefinedOnObjectClass(method)) {
return;
}
if (method.getDeclaringClass().equals(Proxy.class)) {
return;
}
if (!Modifier.isPublic(method.getModifiers())) {
return;
}
if (requiresReply && void.class.equals(method.getReturnType())) {
return;
}
if (methodName != null && !methodName.equals(method.getName())) {
return;
}
if (annotationType != null && AnnotationUtils.findAnnotation(method, annotationType) != null) {
matchesAnnotation = true;
}
HandlerMethod handlerMethod = null;
try {
handlerMethod = new HandlerMethod(method);
}
catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Method [" + method + "] is not eligible for container handling.", e);
}
return;
}
Class<?> targetParameterType = handlerMethod.getTargetParameterType();
if (matchesAnnotation || annotationType == null) {
if (handlerMethod.isMessageMethod()) {
if (candidateMessageMethods.containsKey(targetParameterType)) {
throw new IllegalArgumentException("Found more than one method match for type " +
"[Message<" + targetParameterType + ">]");
}
candidateMessageMethods.put(targetParameterType, handlerMethod);
} else {
if (candidateMethods.containsKey(targetParameterType)) {
String exceptionMessage = "Found more than one method match for ";
if (Void.class.equals(targetParameterType)) {
exceptionMessage += "empty parameter for 'payload'";
} else {
exceptionMessage += "type [" + targetParameterType + "]";
}
throw new IllegalArgumentException(exceptionMessage);
}
candidateMethods.put(targetParameterType, handlerMethod);
}
}
}
}, methodFilter);
if (!candidateMethods.isEmpty() || !candidateMessageMethods.isEmpty()) {
handlerMethods.put(CANDIDATE_METHODS, candidateMethods);
handlerMethods.put(CANDIDATE_MESSAGE_METHODS, candidateMessageMethods);
return handlerMethods;
}
Assert.state(!handlerMethods.isEmpty(), "Target object of type [" + this.targetObject.getClass()
+ "] has no eligible methods for handling Container.");
return handlerMethods;
}
private Class<?> getTargetClass(Object targetObject) {
Class<?> targetClass = targetObject.getClass();
if (AopUtils.isAopProxy(targetObject)) {
targetClass = AopUtils.getTargetClass(targetObject);
if (targetClass == targetObject.getClass()) {
try {
// Maybe a proxy with no target - e.g. gateway
Class<?>[] interfaces = ((Advised) targetObject).getProxiedInterfaces();
if (interfaces != null && interfaces.length == 1) {
targetClass = interfaces[0];
}
}
catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("Exception trying to extract interface", e);
}
}
}
}
else if (org.springframework.util.ClassUtils.isCglibProxyClass(targetClass)) {
Class<?> superClass = targetObject.getClass().getSuperclass();
if (!Object.class.equals(superClass)) {
targetClass = superClass;
}
}
return targetClass;
}
private HandlerMethod findHandlerMethodForParameters(ParametersWrapper parameters) {
if (this.handlerMethod != null) {
return this.handlerMethod;
} else {
return this.handlerMethods.get(Void.class);
}
}
private static boolean isMethodDefinedOnObjectClass(Method method) {
if (method == null) {
return false;
}
if (method.getDeclaringClass().equals(Object.class)) {
return true;
}
if (ReflectionUtils.isEqualsMethod(method) || ReflectionUtils.isHashCodeMethod(method)
|| ReflectionUtils.isToStringMethod(method) || AopUtils.isFinalizeMethod(method)) {
return true;
}
return (method.getName().equals("clone") && method.getParameterTypes().length == 0);
}
/**
* Helper class for generating and exposing metadata for a candidate handler method. The metadata includes the SpEL
* expression and the expected payload type.
*/
private static class HandlerMethod {
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
private final Method method;
private final Expression expression;
private volatile TypeDescriptor targetParameterTypeDescriptor;
private volatile Class<?> targetParameterType = Void.class;
private volatile boolean messageMethod;
HandlerMethod(Method method) {
this.method = method;
this.expression = this.generateExpression(method);
}
Expression getExpression() {
return this.expression;
}
Class<?> getTargetParameterType() {
return this.targetParameterType;
}
private boolean isMessageMethod() {
return messageMethod;
}
@Override
public String toString() {
return this.method.toString();
}
private Expression generateExpression(Method method) {
StringBuilder sb = new StringBuilder("#target." + method.getName() + "(");
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
boolean hasUnqualifiedMapParameter = false;
for (int i = 0; i < parameterTypes.length; i++) {
if (i != 0) {
sb.append(", ");
}
MethodParameter methodParameter = new MethodParameter(method, i);
TypeDescriptor parameterTypeDescriptor = new TypeDescriptor(methodParameter);
Class<?> parameterType = parameterTypeDescriptor.getObjectType();
Annotation mappingAnnotation = findMappingAnnotation(parameterAnnotations[i]);
if (mappingAnnotation != null) {
Class<? extends Annotation> annotationType = mappingAnnotation.annotationType();
// if (annotationType.equals(YarnEnvironments.class)) {
// sb.append("environment");
// } else if (annotationType.equals(YarnEnvironment.class)) {
// YarnEnvironment headerAnnotation = (YarnEnvironment) mappingAnnotation;
// sb.append(this.determineEnvironmentExpression(headerAnnotation, methodParameter));
// } else if (annotationType.equals(YarnParameters.class)) {
// Assert.isTrue(Map.class.isAssignableFrom(parameterType),
// "The @YarnParameters annotation can only be applied to a Map-typed parameter.");
// sb.append("parameters");
// } else if (annotationType.equals(YarnParameter.class)) {
// YarnParameter headerAnnotation = (YarnParameter) mappingAnnotation;
// sb.append(this.determineParameterExpression(headerAnnotation, methodParameter));
// }
}
}
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.");
}
}
sb.append(")");
if (this.targetParameterTypeDescriptor == null) {
this.targetParameterTypeDescriptor = TypeDescriptor.valueOf(Void.class);
}
return EXPRESSION_PARSER.parseExpression(sb.toString());
}
private Annotation findMappingAnnotation(Annotation[] annotations) {
if (annotations == null || annotations.length == 0) {
return null;
}
Annotation match = null;
for (Annotation annotation : annotations) {
Class<? extends Annotation> type = annotation.annotationType();
// if (type.equals(YarnParameters.class) || type.equals(YarnParameter.class)
// || type.equals(YarnEnvironments.class) || type.equals(YarnEnvironment.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;
}
// private String determineParameterExpression(YarnParameter parameterAnnotation, MethodParameter methodParameter) {
// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
// String headerName = null;
// String relativeExpression = "";
// String valueAttribute = parameterAnnotation.value();
// if (!StringUtils.hasText(valueAttribute)) {
// headerName = methodParameter.getParameterName();
// } else if (valueAttribute.indexOf('.') != -1) {
// String tokens[] = valueAttribute.split("\\.", 2);
// headerName = tokens[0];
// if (StringUtils.hasText(tokens[1])) {
// relativeExpression = "." + tokens[1];
// }
// } else {
// headerName = valueAttribute;
// }
// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is "
// + "disabled or header name is not explicitly provided via @YarnParameter annotation.");
// String headerRetrievalExpression = "parameters['" + headerName + "']";
// String fullHeaderExpression = headerRetrievalExpression + relativeExpression;
// String fallbackExpression = (parameterAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: "
// + headerName + "')"
// : "null";
// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression;
// }
// private String determineEnvironmentExpression(YarnEnvironment environmentAnnotation, MethodParameter methodParameter) {
// methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
// String headerName = null;
// String relativeExpression = "";
// String valueAttribute = environmentAnnotation.value();
// if (!StringUtils.hasText(valueAttribute)) {
// headerName = methodParameter.getParameterName();
// } else if (valueAttribute.indexOf('.') != -1) {
// String tokens[] = valueAttribute.split("\\.", 2);
// headerName = tokens[0];
// if (StringUtils.hasText(tokens[1])) {
// relativeExpression = "." + tokens[1];
// }
// } else {
// headerName = valueAttribute;
// }
// Assert.notNull(headerName, "Cannot determine parameter name. Possible reasons: -debug is "
// + "disabled or header name is not explicitly provided via @YarnEnvironment annotation.");
// String headerRetrievalExpression = "environment['" + headerName + "']";
// String fullHeaderExpression = headerRetrievalExpression + relativeExpression;
// String fallbackExpression = (environmentAnnotation.required()) ? "T(org.springframework.util.Assert).isTrue(false, 'required parameter not available: "
// + headerName + "')"
// : "null";
// return headerRetrievalExpression + " != null ? " + fullHeaderExpression + " : " + fallbackExpression;
// }
}
/**
* Wrapping everything we need to work with spel.
*/
public class ParametersWrapper {
private final StateContext stateContext;
public ParametersWrapper(StateContext stateContext) {
this.stateContext = stateContext;
}
public StateContext getStateContext() {
return stateContext;
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.reflect.Method;
import org.springframework.statemachine.annotation.OnTransition;
public class StateMachineOnTransitionHandler extends StateMachineHandler {
private OnTransition annotation;
public StateMachineOnTransitionHandler(Object target, Method method, OnTransition annotation) {
super(target, method);
this.annotation = annotation;
}
public OnTransition getAnnotation() {
return annotation;
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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 org.springframework.statemachine.StateContext;
/**
* A generic runtime representation of a state machine.
*
* @author Janne Valkealahti
*
*/
public interface StateMachineRuntime {
StateContext getStateContext();
}

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.processor;
/**
* Defines a strategy of processing a state machine and returning
* some Object (or null).
*
* @author Janne Valkealahti
*
* @param <T> type
*/
public interface StateMachineRuntimeProcessor<T> {
/**
* Process the container based on information available
* from {@link StateMachineRuntime}.
*
* @param stateMachineRuntime the yarn container runtime
* @return the result
*/
T process(StateMachineRuntime stateMachineRuntime);
}

View File

@@ -0,0 +1,70 @@
/*
* 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.state;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
public abstract class AbstractState<S, E> implements State<S, E> {
private S id;
private Collection<E> deferred;
private Collection<Action> entryActions;
private Collection<Action> exitActions;
public AbstractState(S id) {
this(id, null);
}
public AbstractState(S id, Collection<E> deferred) {
this(id, deferred, null, null);
}
public AbstractState(S id, Collection<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions) {
this.id = id;
this.deferred = deferred;
this.entryActions = entryActions;
this.exitActions = exitActions;
}
@Override
public S getId() {
return id;
}
@Override
public Collection<E> getDeferredEvents() {
return deferred;
}
@Override
public Collection<Action> getEntryActions() {
return entryActions;
}
@Override
public Collection<Action> getExitActions() {
return exitActions;
}
@Override
public String toString() {
return "AbstractState [id=" + id + ", deferred=" + deferred + ", entryActions=" + entryActions
+ ", exitActions=" + exitActions + "]";
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.state;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
public class EnumState<S extends Enum<S>, E extends Enum<E>> extends AbstractState<S, E> {
public EnumState(S id) {
super(id);
}
public EnumState(S id, Collection<E> deferred) {
super(id, deferred);
}
public EnumState(S id, Collection<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions) {
super(id, deferred, entryActions, exitActions);
}
@Override
public String toString() {
return "EnumState [getId()=" + getId() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
+ ", toString()=" + super.toString() + "]";
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.state;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
/**
* {@code State} is an interface representing possible state in a state machine.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface State<S, E> {
/**
* Gets the state identifier.
*
* @return the identifier
*/
S getId();
/**
* Gets the deferred events for this state.
*
* @return the state deferred events
*/
Collection<E> getDeferredEvents();
/**
* Gets {@link Action}s executed entering in this state.
*
* @return the state entry actions
*/
Collection<Action> getEntryActions();
/**
* Gets {@link Action}s executed exiting from this state.
*
* @return the state exit actions
*/
Collection<Action> getExitActions();
}

View File

@@ -0,0 +1,129 @@
/*
* 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.support;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Base class providing common functionality for using Spring expression language.
*
* @author Mark Fisher
* @author Dave Syer
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
* @author Janne Valkealahti
*
*/
public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, InitializingBean {
private volatile StandardEvaluationContext evaluationContext;
private final ExpressionParser expressionParser = new SpelExpressionParser();
private final BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter();
private volatile BeanFactory beanFactory;
@Override
public void setBeanFactory(final BeanFactory beanFactory) {
if (beanFactory != null) {
this.beanFactory = beanFactory;
this.typeConverter.setBeanFactory(beanFactory);
if (this.evaluationContext != null && this.evaluationContext.getBeanResolver() == null) {
this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
getEvaluationContext();
}
/**
* Sets the conversion service.
*
* @param conversionService the new conversion service
*/
public void setConversionService(ConversionService conversionService) {
if (conversionService != null) {
this.typeConverter.setConversionService(conversionService);
}
}
/**
* Gets the evaluation context.
*
* @return the evaluation context
*/
protected StandardEvaluationContext getEvaluationContext() {
return this.getEvaluationContext(true);
}
/**
* Emits a WARN log if the beanFactory field is null, unless the argument is false.
* @param beanFactoryRequired set to false to suppress the warning.
* @return The evaluation context.
*/
protected final StandardEvaluationContext getEvaluationContext(boolean beanFactoryRequired) {
if (this.evaluationContext == null) {
if (this.beanFactory == null && !beanFactoryRequired) {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext();
}
else {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
}
if (this.typeConverter != null) {
this.evaluationContext.setTypeConverter(this.typeConverter);
}
}
return this.evaluationContext;
}
protected Object evaluateExpression(String expression, Object input) {
return this.evaluateExpression(expression, input, (Class<?>) null);
}
protected <T> T evaluateExpression(String expression, Object input, Class<T> expectedType) {
return this.expressionParser.parseExpression(expression).getValue(this.getEvaluationContext(), input, expectedType);
}
protected Object evaluateExpression(Expression expression, Object input) {
return this.evaluateExpression(expression, input, (Class<?>) null);
}
protected <T> T evaluateExpression(Expression expression, Class<T> expectedType) {
return expression.getValue(this.getEvaluationContext(), expectedType);
}
protected Object evaluateExpression(Expression expression) {
return expression.getValue(this.getEvaluationContext());
}
protected <T> T evaluateExpression(Expression expression, Object input, Class<T> expectedType) {
return expression.getValue(this.getEvaluationContext(), input, expectedType);
}
}

View File

@@ -0,0 +1,326 @@
/*
* 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.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.core.OrderComparator;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.listener.CompositeStateMachineListener;
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.state.State;
import org.springframework.statemachine.transition.Transition;
import org.springframework.statemachine.transition.TransitionKind;
import org.springframework.statemachine.trigger.Trigger;
import org.springframework.util.Assert;
/**
* Base implementation of a {@link StateMachine} loosely modelled from UML state
* machine.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport implements StateMachine<State<S,E>, E> {
private static final Log log = LogFactory.getLog(AbstractStateMachine.class);
private final Collection<State<S,E>> states;
private final Collection<Transition<S,E>> transitions;
private final State<S,E> initialState;
private final ExtendedState extendedState;
private final Queue<Message<E>> eventQueue = new ConcurrentLinkedQueue<Message<E>>();
private final LinkedList<Message<E>> deferList = new LinkedList<Message<E>>();
private final CompositeStateMachineListener<S, E> stateListener = new CompositeStateMachineListener<S, E>();
private volatile State<S,E> currentState;
private volatile Runnable task;
/**
* Instantiates a new abstract state machine.
*
* @param states the states of this machine
* @param transitions the transitions of this machine
* @param initialState the initial state of this machine
*/
public AbstractStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState) {
this(states, transitions, initialState, new DefaultExtendedState());
}
/**
* Instantiates a new abstract state machine.
*
* @param states the states of this machine
* @param transitions the transitions of this machine
* @param initialState the initial state of this machine
* @param extendedState the extended state of this machine
*/
public AbstractStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState, ExtendedState extendedState) {
super();
this.states = states;
this.transitions = transitions;
this.initialState = initialState;
this.extendedState = extendedState;
}
@Override
public State<S,E> getState() {
return currentState;
}
@Override
public State<S,E> getInitialState() {
return initialState;
}
@Override
public void sendEvent(Message<E> event) {
// TODO: machine header looks weird!
event = MessageBuilder.fromMessage(event).setHeader("machine", this).build();
if (log.isDebugEnabled()) {
log.debug("Queue event " + event);
}
eventQueue.add(event);
scheduleEventQueueProcessing();
}
@Override
public void sendEvent(E event) {
sendEvent(MessageBuilder.withPayload(event).build());
}
@Override
protected void doStart() {
super.doStart();
switchToState(initialState, null);
}
@Override
public void addStateListener(StateMachineListener<State<S, E>, E> listener) {
stateListener.register(listener);
}
/**
* Gets the {@link State}s defined in this machine. Returned collection is
* an unmodifiable copy because states in a state machine are immutable.
*
* @return immutable copy of existing states
*/
public Collection<State<S,E>> getStates() {
return Collections.unmodifiableCollection(states);
}
private void switchToState(State<S,E> state, Message<E> event) {
log.info("Moving into state=" + state + " from " + currentState);
exitFromState(currentState, event);
stateListener.stateChanged(currentState, state);
callHandlers(currentState, state, event);
currentState = state;
entryToState(state, event);
for (Transition<S,E> transition : transitions) {
State<S,E> source = transition.getSource();
State<S,E> target = transition.getTarget();
if (transition.getTrigger() == null && source.equals(currentState)) {
switchToState(target, event);
}
}
}
private void exitFromState(State<S, E> state, Message<E> event) {
if (state != null) {
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
Collection<Action> actions = state.getExitActions();
if (actions != null) {
for (Action action : actions) {
action.execute(new DefaultStateContext(messageHeaders, extendedState));
}
}
}
}
private void entryToState(State<S,E> state, Message<E> event) {
if (state != null) {
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
Collection<Action> actions = state.getEntryActions();
if (actions != null) {
for (Action action : actions) {
action.execute(new DefaultStateContext(messageHeaders, extendedState));
}
}
}
}
private void processEventQueue() {
log.debug("Process event queue");
Message<E> queuedEvent = null;
while ((queuedEvent = eventQueue.poll()) != null) {
Message<E> defer = null;
for (Transition<S,E> transition : transitions) {
State<S,E> source = transition.getSource();
State<S,E> target = transition.getTarget();
Trigger<S, E> trigger = transition.getTrigger();
if (source.equals(currentState)) {
if (trigger != null && trigger.evaluate(queuedEvent.getPayload())) {
boolean transit = transition.transit(new DefaultStateContext(queuedEvent.getHeaders(), extendedState));
if (transit && transition.getKind() != TransitionKind.INTERNAL) {
switchToState(target, queuedEvent);
}
break;
} else if (source.getDeferredEvents() != null && source.getDeferredEvents().contains(queuedEvent.getPayload())) {
defer = queuedEvent;
}
}
}
if (defer != null) {
log.info("Deferring event " + defer);
deferList.addLast(defer);
}
}
}
private void processDeferList() {
log.debug("Process defer list");
ListIterator<Message<E>> iterator = deferList.listIterator();
while (iterator.hasNext()) {
Message<E> event = iterator.next();
for (Transition<S,E> transition : transitions) {
State<S,E> source = transition.getSource();
State<S,E> target = transition.getTarget();
Trigger<S, E> trigger = transition.getTrigger();
if (source.equals(currentState)) {
if (trigger != null && trigger.evaluate(event.getPayload())) {
boolean transit = transition.transit(new DefaultStateContext(event.getHeaders(), extendedState));
if (transit && transition.getKind() != TransitionKind.INTERNAL) {
switchToState(target, event);
}
iterator.remove();
}
}
}
}
}
private void scheduleEventQueueProcessing() {
if (task == null) {
task = new Runnable() {
@Override
public void run() {
processEventQueue();
processDeferList();
task = null;
}
};
getTaskExecutor().execute(task);
}
}
private void callHandlers(State<S,E> sourceState, State<S,E> targetState, Message<E> event) {
if (sourceState != null && targetState != null) {
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
StateContext stateContext = new DefaultStateContext(messageHeaders, extendedState);
getStateMachineHandlerResults(getStateMachineHandlers(sourceState, targetState), stateContext);
}
}
private List<Object> getStateMachineHandlerResults(List<StateMachineHandler> stateMachineHandlers, final StateContext stateContext) {
StateMachineRuntime runtime = new StateMachineRuntime() {
@Override
public StateContext getStateContext() {
return stateContext;
}
};
List<Object> results = new ArrayList<Object>();
for (StateMachineHandler handler : stateMachineHandlers) {
results.add(handler.handle(runtime));
}
return results;
}
private List<StateMachineHandler> 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");
Map<String, StateMachineOnTransitionHandler> handlers = ((ListableBeanFactory) beanFactory)
.getBeansOfType(StateMachineOnTransitionHandler.class);
List<StateMachineHandler> handlersList = new ArrayList<StateMachineHandler>();
for (Entry<String, StateMachineOnTransitionHandler> entry : handlers.entrySet()) {
OnTransition annotation = entry.getValue().getAnnotation();
String source = annotation.source();
String target = annotation.target();
String s = sourceState.getId().toString();
String t = targetState.getId().toString();
if (s.equals(source) && t.equals(target)) {
handlersList.add(entry.getValue());
}
}
OrderComparator comparator = new OrderComparator();
Collections.sort(handlersList, comparator);
return handlersList;
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.MethodFilter;
import org.springframework.util.StringUtils;
/**
* A MethodFilter implementation that enables the following:
* <ol>
* <li>matching on method name, if available</li>
* <li>exclusion of void-returning methods if 'requiresReply' is true</li>
* <li>limiting to annotated methods if at least one is present</li>
* </ol>
* <p>
*
* @author Mark Fisher
* @author Janne Valkealahti
*/
public class AnnotatedMethodFilter implements MethodFilter {
private final Class<? extends Annotation> annotationType;
private final String methodName;
private final boolean requiresReply;
public AnnotatedMethodFilter(Class<? extends Annotation> annotationType, String methodName, boolean requiresReply) {
this.annotationType = annotationType;
this.methodName = methodName;
this.requiresReply = requiresReply;
}
public List<Method> filter(List<Method> methods) {
List<Method> annotatedCandidates = new ArrayList<Method>();
List<Method> fallbackCandidates = new ArrayList<Method>();
for (Method method : methods) {
if (method.isBridge()) {
continue;
}
if (this.requiresReply && method.getReturnType().equals(void.class)) {
continue;
}
if (StringUtils.hasText(this.methodName) && !this.methodName.equals(method.getName())) {
continue;
}
if (this.annotationType != null && AnnotationUtils.findAnnotation(method, this.annotationType) != null) {
annotatedCandidates.add(method);
} else {
fallbackCandidates.add(method);
}
}
return (!annotatedCandidates.isEmpty()) ? annotatedCandidates : fallbackCandidates;
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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.support;
import java.beans.PropertyEditor;
import org.springframework.beans.BeansException;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.expression.TypeConverter;
import org.springframework.util.ClassUtils;
/**
* @author Dave Syer
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Soby Chacko
* @author Janne Valkealahti
*
*/
public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
private static ConversionService defaultConversionService;
private volatile SimpleTypeConverter delegate = new SimpleTypeConverter();
private volatile boolean haveCalledDelegateGetDefaultEditor;
private volatile ConversionService conversionService;
/**
* Instantiates a new bean factory type converter.
*/
public BeanFactoryTypeConverter() {
synchronized (BeanFactoryTypeConverter.class) {
if (defaultConversionService == null) {
defaultConversionService = new DefaultConversionService();
}
}
this.conversionService = defaultConversionService;
}
public BeanFactoryTypeConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableBeanFactory) {
Object typeConverter = ((ConfigurableBeanFactory) beanFactory).getTypeConverter();
if (typeConverter instanceof SimpleTypeConverter) {
delegate = (SimpleTypeConverter) typeConverter;
}
}
}
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
if (conversionService.canConvert(sourceType, targetType)) {
return true;
}
if (!String.class.isAssignableFrom(sourceType) && !String.class.isAssignableFrom(targetType)) {
// PropertyEditor cannot convert non-Strings
return false;
}
if (!String.class.isAssignableFrom(sourceType)) {
return delegate.findCustomEditor(sourceType, null) != null || this.getDefaultEditor(sourceType) != null;
}
return delegate.findCustomEditor(targetType, null) != null || this.getDefaultEditor(targetType) != null;
}
public boolean canConvert(TypeDescriptor sourceTypeDescriptor, TypeDescriptor targetTypeDescriptor) {
if (conversionService.canConvert(sourceTypeDescriptor, targetTypeDescriptor)) {
return true;
}
// TODO: what does this mean? This method is not used in SpEL so
// probably ignorable?
Class<?> sourceType = sourceTypeDescriptor.getObjectType();
Class<?> targetType = targetTypeDescriptor.getObjectType();
return canConvert(sourceType, targetType);
}
public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
// Echoes
// org.springframework.expression.common.ExpressionUtils.convertTypedValue()
if ((targetType.getType() == Void.class || targetType.getType() == Void.TYPE) && value == null) {
return null;
}
if (sourceType != null) {
Class<?> sourceClass = sourceType.getType();
//Class<?> targetClass = targetType.getType();
if (sourceType.isAssignableTo(targetType) && ClassUtils.isPrimitiveArray(sourceClass)) {
return value;
}
}
if (conversionService.canConvert(sourceType, targetType)) {
return conversionService.convert(value, sourceType, targetType);
}
if (!String.class.isAssignableFrom(sourceType.getType())) {
PropertyEditor editor = delegate.findCustomEditor(sourceType.getType(), null);
if (editor == null) {
editor = this.getDefaultEditor(sourceType.getType());
}
if (editor != null) { // INT-1441
String text = null;
synchronized (editor) {
editor.setValue(value);
text = editor.getAsText();
}
if (String.class.isAssignableFrom(targetType.getType())) {
return text;
}
return convertValue(text, TypeDescriptor.valueOf(String.class), targetType);
}
}
return delegate.convertIfNecessary(value, targetType.getType());
}
private PropertyEditor getDefaultEditor(Class<?> sourceType) {
PropertyEditor defaultEditor;
if (this.haveCalledDelegateGetDefaultEditor) {
defaultEditor = delegate.getDefaultEditor(sourceType);
} else {
synchronized (this) {
// not thread-safe - it builds the defaultEditors field in-place
// (SPR-10191)
defaultEditor = delegate.getDefaultEditor(sourceType);
}
this.haveCalledDelegateGetDefaultEditor = true;
}
return defaultEditor;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.support;
import java.util.HashMap;
import java.util.Map;
import org.springframework.statemachine.ExtendedState;
/**
* Default implementation of a {@link ExtendedState}.
*
* @author Janne Valkealahti
*
*/
public class DefaultExtendedState implements ExtendedState {
private final Map<String, Object> variables;
/**
* Instantiates a new default extended state.
*/
public DefaultExtendedState() {
this.variables = new HashMap<String, Object>();
}
@Override
public Map<String, Object> getVariables() {
return variables;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.support;
import org.springframework.messaging.MessageHeaders;
import org.springframework.statemachine.ExtendedState;
import org.springframework.statemachine.StateContext;
public class DefaultStateContext implements StateContext {
private final MessageHeaders messageHeaders;
private final ExtendedState extendedState;
public DefaultStateContext(MessageHeaders messageHeaders, ExtendedState extendedState) {
this.messageHeaders = messageHeaders;
this.extendedState = extendedState;
}
@Override
public MessageHeaders getMessageHeaders() {
return messageHeaders;
}
@Override
public ExtendedState getExtendedState() {
return extendedState;
}
}

View File

@@ -0,0 +1,96 @@
/*
* 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.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
/**
* Utility class with static methods for helping with establishing environments for
* SpEL expressions.
*
* @author Gary Russell
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public abstract class ExpressionUtils {
private static final Log logger = LogFactory.getLog(ExpressionUtils.class);
/**
* Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its
* property accessor property and the supplied {@link ConversionService} in its
* conversionService property.
*
* @param conversionService the conversion service.
* @return the evaluation context.
*/
private static StandardEvaluationContext createStandardEvaluationContext(ConversionService conversionService,
BeanFactory beanFactory) {
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.addPropertyAccessor(new MapAccessor());
if (conversionService != null) {
evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService));
}
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
/**
* Used to create a context with no BeanFactory, usually in tests.
* @return The evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext() {
return doCreateContext(null);
}
/**
* Obtains the context from the beanFactory if not null; emits a warning if the beanFactory
* is null.
* @param beanFactory The bean factory.
* @return The evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext(BeanFactory beanFactory) {
if (beanFactory == null) {
logger.warn("Creating EvaluationContext with no beanFactory", new RuntimeException("No beanfactory"));
}
return doCreateContext(beanFactory);
}
private static StandardEvaluationContext doCreateContext(BeanFactory beanFactory) {
ConversionService conversionService = null;
StandardEvaluationContext evaluationContext = null;
if (beanFactory != null) {
evaluationContext = StateMachineContextUtils.getEvaluationContext(beanFactory);
}
if (evaluationContext == null) {
if (beanFactory != null) {
conversionService = StateMachineContextUtils.getConversionService(beanFactory);
}
evaluationContext = createStandardEvaluationContext(conversionService, beanFactory);
}
return evaluationContext;
}
}

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.support;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.expression.MethodFilter;
import org.springframework.util.Assert;
/**
* A {@link MethodFilter} implementation that will always return the same Method
* instance within a single-element list if it is present in the candidate list.
* If the Method is not present in the candidate list, it will return an empty
* list.
*
* @author Mark Fisher
* @author Gary Russell
* @author Janne Valkealahti
*/
public class FixedMethodFilter implements MethodFilter {
private final Method method;
public FixedMethodFilter(Method method) {
Assert.notNull(method, "method must not be null");
this.method = method;
}
public List<Method> filter(List<Method> methods) {
if (methods != null && methods.contains(this.method)) {
List<Method> filteredList = new ArrayList<Method>(1);
filteredList.add(this.method);
return filteredList;
}
return Collections.<Method> emptyList();
}
}

View File

@@ -0,0 +1,250 @@
/*
* 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.support;
import java.util.concurrent.locks.ReentrantLock;
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.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
* Convenient base class for object which needs spring task scheduler, task
* executor and life cycle handling.
*
* @author Janne Valkealahti
*
*/
public abstract class LifecycleObjectSupport implements InitializingBean, SmartLifecycle, BeanFactoryAware {
private static final Log log = LogFactory.getLog(LifecycleObjectSupport.class);
// fields for lifecycle
private volatile boolean autoStartup = true;
private volatile int phase = 0;
private volatile boolean running;
// lock to protect lifycycle methods
private final ReentrantLock lifecycleLock = new ReentrantLock();
// common task handling
private TaskScheduler taskScheduler;
private TaskExecutor taskExecutor;
// to access bean factory
private volatile BeanFactory beanFactory;
@Override
public final void afterPropertiesSet() {
try {
this.onInit();
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new BeanInitializationException("failed to initialize", e);
}
}
@Override
public final void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.notNull(beanFactory, "beanFactory must not be null");
if(log.isDebugEnabled()) {
log.debug("Setting bean factory: " + beanFactory + " for " + this);
}
this.beanFactory = beanFactory;
}
@Override
public final boolean isAutoStartup() {
return this.autoStartup;
}
@Override
public final int getPhase() {
return this.phase;
}
@Override
public final boolean isRunning() {
this.lifecycleLock.lock();
try {
return this.running;
} finally {
this.lifecycleLock.unlock();
}
}
@Override
public final void start() {
this.lifecycleLock.lock();
try {
if (!this.running) {
this.doStart();
this.running = true;
if (log.isInfoEnabled()) {
log.info("started " + this);
} else {
if(log.isDebugEnabled()) {
log.debug("already started " + this);
}
}
}
} finally {
this.lifecycleLock.unlock();
}
}
@Override
public final void stop() {
this.lifecycleLock.lock();
try {
if (this.running) {
this.doStop();
this.running = false;
if (log.isInfoEnabled()) {
log.info("stopped " + this);
}
} else {
if (log.isDebugEnabled()) {
log.debug("already stopped " + this);
}
}
} finally {
this.lifecycleLock.unlock();
}
}
@Override
public final void stop(Runnable callback) {
this.lifecycleLock.lock();
try {
this.stop();
callback.run();
} finally {
this.lifecycleLock.unlock();
}
}
/**
* Sets the auto startup.
*
* @param autoStartup the new auto startup
* @see SmartLifecycle
*/
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
/**
* Sets the phase.
*
* @param phase the new phase
* @see SmartLifecycle
*/
public void setPhase(int phase) {
this.phase = phase;
}
/**
* Gets the {@link BeanFactory} for this instance.
*
* @return the bean factory.
*/
protected final BeanFactory getBeanFactory() {
return beanFactory;
}
/**
* Sets the used {@link TaskScheduler}.
*
* @param taskScheduler the task scheduler
*/
public void setTaskScheduler(TaskScheduler taskScheduler) {
Assert.notNull(taskScheduler, "taskScheduler must not be null");
this.taskScheduler = taskScheduler;
}
/**
* Gets the defined {@link TaskScheduler}.
*
* @return the defined task scheduler
*/
protected TaskScheduler getTaskScheduler() {
if(taskScheduler == null && getBeanFactory() != null) {
if(log.isDebugEnabled()) {
log.debug("getting taskScheduler service from bean factory " + getBeanFactory());
}
taskScheduler = StateMachineContextUtils.getTaskScheduler(getBeanFactory());
}
return taskScheduler;
}
/**
* Sets the used {@link TaskExecutor}.
*
* @param taskExecutor the task executor
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
Assert.notNull(taskExecutor, "taskExecutor must not be null");
this.taskExecutor = taskExecutor;
}
/**
* Gets the defined {@link TaskExecutor}.
*
* @return the defined task executor
*/
protected TaskExecutor getTaskExecutor() {
if(taskExecutor == null && getBeanFactory() != null) {
if(log.isDebugEnabled()) {
log.debug("getting taskExecutor service from bean factory " + getBeanFactory());
}
taskExecutor = StateMachineContextUtils.getTaskExecutor(getBeanFactory());
}
return taskExecutor;
}
/**
* Subclasses may implement this for initialization logic. Called
* during the {@link InitializingBean} phase. Implementor should
* always call super method not to break initialization chain.
*
* @throws Exception exception
*/
protected void onInit() throws Exception {}
/**
* Subclasses may implement this method with the start behavior. This
* method will be invoked while holding the {@link #lifecycleLock}.
*/
protected void doStart() {};
/**
* Subclasses may implement this method with the stop behavior. This method
* will be invoked while holding the {@link #lifecycleLock}.
*/
protected void doStop() {};
}

View File

@@ -0,0 +1,110 @@
/*
* 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.support;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.task.TaskExecutor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
* Utility methods for accessing common components from the BeanFactory.
*
* @author Janne Valkealahti
*
*/
public class StateMachineContextUtils {
/* Default task scheduler bean name */
public static final String TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
/* Default task executor bean name */
public static final String TASK_EXECUTOR_BEAN_NAME = "taskExecutor";
/* Default conversion service bean name */
public static final String CONVERSION_SERVICE_BEAN_NAME = "cloudClusterConversionService";
/* Default evaluation context bean name */
public static final String EVALUATION_CONTEXT_BEAN_NAME = "cloudClusterEvaluationContext";
/**
* Return the {@link TaskScheduler} bean whose name is "taskScheduler" if
* available.
*
* @param beanFactory BeanFactory for lookup, must not be null.
* @return task scheduler
*/
public static TaskScheduler getTaskScheduler(BeanFactory beanFactory) {
return getBeanOfType(beanFactory, TASK_SCHEDULER_BEAN_NAME, TaskScheduler.class);
}
/**
* Return the {@link TaskScheduler} bean whose name is "taskExecutor" if
* available.
*
* @param beanFactory BeanFactory for lookup, must not be null.
* @return task executor
*/
public static TaskExecutor getTaskExecutor(BeanFactory beanFactory) {
return getBeanOfType(beanFactory, TASK_EXECUTOR_BEAN_NAME, TaskExecutor.class);
}
/**
* Return the {@link ConversionService} bean whose name is
* "yarnConversionService" if available.
*
* @param beanFactory
* BeanFactory for lookup, must not be null.
*
* @return The {@link ConversionService} bean whose name is
* "yarnConversionService" if available.
*/
public static ConversionService getConversionService(BeanFactory beanFactory) {
return getBeanOfType(beanFactory, CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
}
/**
* Return the {@link StandardEvaluationContext} bean whose name is
* "yarnEvaluationContext" if available.
*
* @param beanFactory BeanFactory for lookup, must not be null.
*
* @return the instance of {@link StandardEvaluationContext} bean whose name
* is "yarnEvaluationContext" .
*/
public static StandardEvaluationContext getEvaluationContext(BeanFactory beanFactory) {
return getBeanOfType(beanFactory, EVALUATION_CONTEXT_BEAN_NAME, StandardEvaluationContext.class);
}
/**
* Gets a bean from a factory with a given name and type.
*
* @param beanFactory the bean factory
* @param beanName the bean name
* @param type the type as of a class
* @return Bean known to a bean factory, null if not found.
*/
private static <T> T getBeanOfType(BeanFactory beanFactory, String beanName, Class<T> type) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
if (!beanFactory.containsBean(beanName)) {
return null;
}
return beanFactory.getBean(beanName, type);
}
}

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.support;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
* A {@link MethodFilter} implementation that will match unique methods.
*
* @author Oleg Zhurakousky
* @author Janne Valkealahti
*/
public class UniqueMethodFilter implements MethodFilter {
private final List<Method> uniqueMethods = new ArrayList<Method>();
public UniqueMethodFilter(Class<?> targetClass) {
ArrayList<Method> allMethods = new ArrayList<Method>(Arrays.asList(targetClass.getMethods()));
for (Method method : allMethods) {
this.uniqueMethods.add(org.springframework.util.ClassUtils.getMostSpecificMethod(method, targetClass));
}
}
public boolean matches(Method method) {
return this.uniqueMethods.contains(method);
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.transition;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.state.State;
public abstract class AbstractExternalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
public AbstractExternalTransition(State<S,E> source, State<S,E> target, Collection<Action> actions, E event, Guard guard) {
super(source, target, actions, event, TransitionKind.EXTERNAL, guard);
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.transition;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.state.State;
public class AbstractInternalTransition <S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
public AbstractInternalTransition(State<S, E> source,Collection<Action> actions, E event, Guard guard) {
super(source, source, actions, event, TransitionKind.INTERNAL, guard);
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.transition;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.state.State;
public class AbstractLocalTransition <S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
public AbstractLocalTransition(State<S, E> source, State<S,E> target,Collection<Action> actions, E event, Guard guard) {
super(source, target, actions, event, TransitionKind.LOCAL, guard);
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.transition;
import java.util.Collection;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.trigger.EventTrigger;
import org.springframework.statemachine.trigger.Trigger;
import org.springframework.util.Assert;
/**
* Base implementation of a {@link Transition}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractTransition<S, E> implements Transition<S, E> {
private final State<S,E> source;
private final State<S,E> target;
private final Collection<Action> actions;
private final TransitionKind kind;
private final Guard guard;
private Trigger<S, E> trigger;
public AbstractTransition(State<S, E> source, State<S, E> target, Collection<Action> actions, E event,
TransitionKind kind, Guard guard) {
Assert.notNull(source, "Source must be set");
// Assert.notNull(target, "Target must be set");
Assert.notNull(kind, "Transition type must be set");
this.source = source;
this.target = target;
this.actions = actions;
if (event != null) {
this.trigger = new EventTrigger<S, E>(event);
}
this.kind = kind;
this.guard = guard;
}
@Override
public State<S,E> getSource() {
return source;
}
@Override
public State<S,E> getTarget() {
return target;
}
@Override
public Collection<Action> getActions() {
return actions;
}
@Override
public Trigger<S, E> getTrigger() {
return trigger;
}
@Override
public boolean transit(StateContext context) {
if (guard != null) {
if (!guard.evaluate(context)) {
return false;
}
}
if (actions != null) {
for (Action action : actions) {
action.execute(context);
}
}
return true;
}
@Override
public TransitionKind getKind() {
return kind;
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.transition;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.state.State;
public class DefaultExternalTransition<S, E> extends AbstractExternalTransition<S, E> {
public DefaultExternalTransition(State<S,E> source, State<S,E> target, Collection<Action> actions, E event, Guard guard) {
super(source, target, actions, event, guard);
}
}

View File

@@ -0,0 +1,30 @@
/*
* 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.transition;
import java.util.Collection;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.state.State;
public class DefaultInternalTransition<S, E> extends AbstractInternalTransition<S, E> {
public DefaultInternalTransition(State<S, E> source, Collection<Action> actions, E event, Guard guard) {
super(source, actions, event, guard);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.transition;
import java.util.Collection;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.trigger.Trigger;
/**
* {@code Transition} is something what a state machine associates with a state
* changes.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface Transition<S, E> {
boolean transit(StateContext context);
State<S,E> getSource();
State<S,E> getTarget();
Collection<Action> getActions();
Trigger<S, E> getTrigger();
TransitionKind getKind();
}

View File

@@ -0,0 +1,36 @@
/*
* 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.transition;
/**
* Defines enumeration of a {@link Transition} kind. This is uses within a
* transition to indicate whether its type is external, internal or local.
*
* @author Janne Valkealahti
*
*/
public enum TransitionKind {
/** Indicates an external transition kind. */
EXTERNAL,
/** Indicates an internal transition kind. */
INTERNAL,
/** Indicates a local transition kind. */
LOCAL
}

View File

@@ -0,0 +1,31 @@
/*
* 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.trigger;
public class EventTrigger<S, E> implements Trigger<S, E> {
private final E event;
public EventTrigger(E event) {
this.event = event;
}
@Override
public boolean evaluate(E event) {
return this.event.equals(event);
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.trigger;
import org.springframework.statemachine.transition.Transition;
/**
* {@code Trigger} is the cause of the {@link Transition}. Cause is usually an
* event but can be some other signal or a change in some condition.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface Trigger<S,E> {
boolean evaluate(E event);
}

View File

@@ -0,0 +1,93 @@
/*
* 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;
import java.util.concurrent.CountDownLatch;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.guard.Guard;
/**
* Base class for stace machine tests.
*
* @author Janne Valkealahti
*
*/
public abstract class AbstractStateMachineTests {
public enum TestStates {
SI,S1,S2,S3,S4
}
public enum TestEvents {
E1,E2,E3,E4
}
@Configuration
public static class BaseConfig {
@Bean
public TaskExecutor taskExecutor() {
return new SyncTaskExecutor();
}
}
public static class TestEntryAction extends AbstractTestAction {
}
public static class TestExitAction extends AbstractTestAction {
}
public static class TestAction extends AbstractTestAction {
}
public static class TestGuard implements Guard {
public CountDownLatch onEvaluateLatch = new CountDownLatch(1);
boolean evaluationResult = true;
public TestGuard() {
}
public TestGuard(boolean evaluationResult) {
this.evaluationResult = evaluationResult;
}
@Override
public boolean evaluate(StateContext context) {
onEvaluateLatch.countDown();
return evaluationResult;
}
}
protected static class AbstractTestAction implements Action {
public CountDownLatch onExecuteLatch = new CountDownLatch(1);
@Override
public void execute(StateContext context) {
onExecuteLatch.countDown();
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* 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;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.EnumStateMachine;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.state.EnumState;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.DefaultExternalTransition;
import org.springframework.statemachine.transition.DefaultInternalTransition;
import org.springframework.statemachine.transition.Transition;
public class EnumStateMachineTests extends AbstractStateMachineTests {
@Test
public void testSimpleStateSwitch() {
State<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI);
State<TestStates,TestEvents> stateS1 = new EnumState<TestStates,TestEvents>(TestStates.S1);
State<TestStates,TestEvents> stateS2 = new EnumState<TestStates,TestEvents>(TestStates.S2);
State<TestStates,TestEvents> stateS3 = new EnumState<TestStates,TestEvents>(TestStates.S3);
Collection<State<TestStates,TestEvents>> states = new ArrayList<State<TestStates,TestEvents>>();
states.add(stateSI);
states.add(stateS1);
states.add(stateS2);
states.add(stateS3);
Collection<Transition<TestStates,TestEvents>> transitions = new ArrayList<Transition<TestStates,TestEvents>>();
Collection<Action> actionsFromSIToS1 = new ArrayList<Action>();
actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1"));
DefaultExternalTransition<TestStates,TestEvents> transitionFromSIToS1 =
new DefaultExternalTransition<TestStates,TestEvents>(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null);
Collection<Action> actionsFromS1ToS2 = new ArrayList<Action>();
actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2"));
DefaultExternalTransition<TestStates,TestEvents> transitionFromS1ToS2 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null);
Collection<Action> actionsFromS2ToS3 = new ArrayList<Action>();
actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3"));
DefaultExternalTransition<TestStates,TestEvents> transitionFromS2ToS3 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null);
transitions.add(transitionFromSIToS1);
transitions.add(transitionFromS1ToS2);
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.start();
State<TestStates,TestEvents> initialState = machine.getInitialState();
assertThat(initialState, is(stateSI));
State<TestStates,TestEvents> state = machine.getState();
assertThat(state, is(stateSI));
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
state = machine.getState();
assertThat(state, is(stateS1));
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build());
state = machine.getState();
assertThat(state, is(stateS2));
// not processed
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
state = machine.getState();
assertThat(state, is(stateS2));
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build());
state = machine.getState();
assertThat(state, is(stateS3));
}
@Test
public void testDeferredEvents() {
Collection<TestEvents> deferred = new ArrayList<TestEvents>();
deferred.add(TestEvents.E2);
deferred.add(TestEvents.E3);
// states
State<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI, deferred);
State<TestStates,TestEvents> stateS1 = new EnumState<TestStates,TestEvents>(TestStates.S1);
State<TestStates,TestEvents> stateS2 = new EnumState<TestStates,TestEvents>(TestStates.S2);
State<TestStates,TestEvents> stateS3 = new EnumState<TestStates,TestEvents>(TestStates.S3);
Collection<State<TestStates,TestEvents>> states = new ArrayList<State<TestStates,TestEvents>>();
states.add(stateSI);
states.add(stateS1);
states.add(stateS2);
states.add(stateS3);
// transitions
Collection<Transition<TestStates,TestEvents>> transitions = new ArrayList<Transition<TestStates,TestEvents>>();
Collection<Action> actionsFromSIToS1 = new ArrayList<Action>();
actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1"));
DefaultExternalTransition<TestStates,TestEvents> transitionFromSIToS1 =
new DefaultExternalTransition<TestStates,TestEvents>(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null);
Collection<Action> actionsFromS1ToS2 = new ArrayList<Action>();
actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2"));
DefaultExternalTransition<TestStates,TestEvents> transitionFromS1ToS2 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null);
Collection<Action> actionsFromS2ToS3 = new ArrayList<Action>();
actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3"));
DefaultExternalTransition<TestStates,TestEvents> transitionFromS2ToS3 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null);
transitions.add(transitionFromSIToS1);
transitions.add(transitionFromS1ToS2);
transitions.add(transitionFromS2ToS3);
// create machine
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
// StateMachine<State<TestStates, TestEvents>, TestEvents> machine2 = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.start();
State<TestStates,TestEvents> initialState = machine.getInitialState();
assertThat(initialState, is(stateSI));
State<TestStates,TestEvents> state = machine.getState();
assertThat(state, is(stateSI));
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build());
state = machine.getState();
assertThat(state, is(stateSI));
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
state = machine.getState();
assertThat(state, is(stateS3));
}
@Test
public void testInternalTransitions() {
State<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI);
Collection<State<TestStates,TestEvents>> states = new ArrayList<State<TestStates,TestEvents>>();
states.add(stateSI);
Collection<Action> actionsInSI = new ArrayList<Action>();
actionsInSI.add(new LoggingAction("actionsInSI"));
DefaultInternalTransition<TestStates,TestEvents> transitionInternalSI =
new DefaultInternalTransition<TestStates,TestEvents>(stateSI, actionsInSI, TestEvents.E1, null);
// transitions
Collection<Transition<TestStates,TestEvents>> transitions = new ArrayList<Transition<TestStates,TestEvents>>();
transitions.add(transitionInternalSI);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.start();
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
}
private static class LoggingAction implements Action {
private static final Log log = LogFactory.getLog(LoggingAction.class);
private String message;
public LoggingAction(String message) {
this.message = message;
}
@Override
public void execute(StateContext context) {
log.info("Hello from LoggingAction " + message);
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.EnumStateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.state.State;
public class StateMachineFactoryTests extends AbstractStateMachineTests {
@SuppressWarnings({ "unchecked" })
@Test
public void testMachineFromFactory() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
EnumStateMachineFactory<TestStates, TestEvents> stateMachineFactory =
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, EnumStateMachineFactory.class);
StateMachine<State<TestStates, TestEvents>, TestEvents> machine = stateMachineFactory.getStateMachine();
assertThat(machine.getState().getId(), is(TestStates.S1));
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(machine.getState().getId(), is(TestStates.S2));
ctx.close();
}
@Configuration
@EnableStateMachineFactory
static class Config extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.state(TestStates.S1)
.state(TestStates.S2);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1);
}
@Bean
public TaskExecutor taskExecutor() {
return new SyncTaskExecutor();
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.EnumStateMachine;
import org.springframework.statemachine.StateContext;
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;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public class StateMachineTests extends AbstractStateMachineTests {
@Test
public void test1() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
@SuppressWarnings("unchecked")
EnumStateMachine<TestStates,TestEvents> machine =
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
assertThat(machine, notNullValue());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee1").build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).setHeader("foo", "jee2").build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).setHeader("foo", "jee2").build());
ctx.close();
}
private static class LoggingAction implements Action {
private static final Log log = LogFactory.getLog(StateMachineTests.LoggingAction.class);
private String message;
public LoggingAction(String message) {
this.message = message;
}
@Override
public void execute(StateContext context) {
log.info("Hello from LoggingAction " + message + " foo=" + context.getMessageHeaders().get("foo"));
}
}
@Configuration
@EnableStateMachine
static class Config extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.state(TestStates.S1)
.state(TestStates.S2)
.state(TestStates.S3, TestEvents.E4)
.state(TestStates.S4);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1)
.action(loggingAction())
.action(loggingAction())
.and()
.withExternal()
.source(TestStates.S2)
.target(TestStates.S3)
.event(TestEvents.E2)
.action(loggingAction())
.and()
.withExternal()
.source(TestStates.S3)
.target(TestStates.S4)
.event(TestEvents.E3)
.action(loggingAction())
.and()
.withExternal()
.source(TestStates.S4)
.target(TestStates.S3)
.event(TestEvents.E4)
.action(loggingAction());
}
@Bean
public LoggingAction loggingAction() {
return new LoggingAction("as bean");
}
@Bean
public TaskExecutor taskExecutor() {
return new SyncTaskExecutor();
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.springframework.util.ReflectionUtils;
/**
* Utils for tests.
*
* @author Janne Valkealahti
*
*/
public class TestUtils {
@SuppressWarnings("unchecked")
public static <T> T readField(String name, Object target) throws Exception {
Field field = null;
Class<?> clazz = target.getClass();
do {
try {
field = clazz.getDeclaredField(name);
} catch (Exception ex) {
}
clazz = clazz.getSuperclass();
} while (field == null && !clazz.equals(Object.class));
if (field == null)
throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of "
+ target.getClass());
field.setAccessible(true);
return (T) field.get(target);
}
@SuppressWarnings("unchecked")
public static <T> T callMethod(String name, Object target) throws Exception {
Class<?> clazz = target.getClass();
Method method = ReflectionUtils.findMethod(clazz, name);
if (method == null)
throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of "
+ target.getClass());
method.setAccessible(true);
return (T) ReflectionUtils.invokeMethod(method, target);
}
public static void setField(String name, Object target, Object value) throws Exception {
Field field = null;
Class<?> clazz = target.getClass();
do {
try {
field = clazz.getDeclaredField(name);
} catch (Exception ex) {
}
clazz = clazz.getSuperclass();
} while (field == null && !clazz.equals(Object.class));
if (field == null)
throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of "
+ target.getClass());
field.setAccessible(true);
field.set(target, value);
}
@SuppressWarnings("unchecked")
public static <T> T callMethod(String name, Object target, Object[] args, Class<?>[] argsTypes) throws Exception {
Class<?> clazz = target.getClass();
Method method = ReflectionUtils.findMethod(clazz, name, argsTypes);
if (method == null)
throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of "
+ target.getClass());
method.setAccessible(true);
return (T) ReflectionUtils.invokeMethod(method, target, args);
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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.action;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
/**
* Tests for state machine actions.
*
* @author Janne Valkealahti
*
*/
public class ActionTests extends AbstractStateMachineTests {
@SuppressWarnings({ "unchecked" })
@Test
public void testTransitionActions() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class);
assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
StateMachine<TestStates,TestEvents> machine =
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
TestCountAction testAction1 = ctx.getBean("testAction1", TestCountAction.class);
TestCountAction testAction2 = ctx.getBean("testAction2", TestCountAction.class);
TestCountAction testAction3 = ctx.getBean("testAction3", TestCountAction.class);
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build());
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E3).build());
assertThat(testAction1.count, is(1));
assertThat(testAction2.count, is(1));
assertThat(testAction3.count, is(1));
ctx.close();
}
@Test
public void testEventFromAction() {
}
private static class TestCountAction implements Action {
int count = 0;
public TestCountAction() {
count = 0;
}
@Override
public void execute(StateContext context) {
count++;
}
}
@Configuration
@EnableStateMachine
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.state(TestStates.S1)
.state(TestStates.S2)
.state(TestStates.S3)
.state(TestStates.S4);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1)
.action(testAction1())
.and()
.withExternal()
.source(TestStates.S2)
.target(TestStates.S3)
.event(TestEvents.E2)
.action(testAction2())
.and()
.withExternal()
.source(TestStates.S3)
.target(TestStates.S4)
.event(TestEvents.E3)
.action(testAction3());
}
@Bean
public TestCountAction testAction1() {
return new TestCountAction();
}
@Bean
public TestCountAction testAction2() {
return new TestCountAction();
}
@Bean
public TestCountAction testAction3() {
return new TestCountAction();
}
@Bean
public TaskExecutor taskExecutor() {
return new SyncTaskExecutor();
}
}
}

View File

@@ -0,0 +1,141 @@
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.EnumStateMachine;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.processor.StateMachineAnnotationPostProcessor;
public class MethodAnnotationTests extends AbstractStateMachineTests {
@Test
@SuppressWarnings("unchecked")
public void testMethodAnnotations() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BaseConfig.class, AnnoConfig.class, BeanConfig1.class, Config1.class);
EnumStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
assertThat(context.containsBean("fooMachine"), is(true));
Bean1 bean1 = context.getBean(Bean1.class);
// this event should cause 'method1' to get called
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(bean1.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true));
assertThat(bean1.onOnTransitionFromS2ToS3Latch.await(2, TimeUnit.SECONDS), is(false));
context.close();
}
@WithStateMachine(name = StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)
static class Bean1 {
CountDownLatch onMethod1Latch = new CountDownLatch(1);
CountDownLatch onOnTransitionFromS2ToS3Latch = new CountDownLatch(1);
@OnTransition(source = "S1", target = "S2")
public void method1() {
onMethod1Latch.countDown();
}
@OnTransition
public void onTransitionFromS2ToS3() {
onOnTransitionFromS2ToS3Latch.countDown();
}
}
@Configuration
static class BeanConfig1 {
@Bean
public Bean1 bean1() {
return new Bean1();
}
}
@Configuration
static class AnnoConfig {
@Bean(name="org.springframework.statemachine.internal.springStateMachineAnnotationPostProcessor")
public StateMachineAnnotationPostProcessor springStateMachineAnnotationPostProcessor() {
return new StateMachineAnnotationPostProcessor();
}
}
@Configuration
@EnableStateMachine(name = {StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, "fooMachine"})
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.states(EnumSet.allOf(TestStates.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1)
.guard(testGuard())
.action(testAction())
.and()
.withExternal()
.source(TestStates.S2)
.target(TestStates.S3)
.event(TestEvents.E2);
}
@Bean
public TestGuard testGuard() {
return new TestGuard();
}
@Bean
public TestAction testAction() {
return new TestAction();
}
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.config;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.EnumSet;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.statemachine.AbstractStateMachineTests;
import org.springframework.statemachine.EnumStateMachine;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
/**
* Tests for state machine configuration.
*
* @author Janne Valkealahti
*
*/
public class ConfigurationTests extends AbstractStateMachineTests {
@SuppressWarnings({ "unchecked" })
@Test
public void testStates() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config1.class);
assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
EnumStateMachine<TestStates,TestEvents> machine =
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
assertThat(machine, notNullValue());
TestAction testAction = ctx.getBean("testAction", TestAction.class);
TestGuard testGuard = ctx.getBean("testGuard", TestGuard.class);
assertThat(testAction, notNullValue());
assertThat(testGuard, notNullValue());
ctx.close();
}
@Configuration
@EnableStateMachine
public static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.state(TestStates.S1)
.state(TestStates.S2)
.state(TestStates.S3)
.state(TestStates.S4);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1)
.guard(testGuard())
.action(testAction());
}
@Bean
public TestAction testAction() {
return new TestAction();
}
@Bean
public TestGuard testGuard() {
return new TestGuard();
}
@Bean
public TaskExecutor taskExecutor() {
return new SyncTaskExecutor();
}
}
@Configuration
@EnableStateMachine
public static class Config2 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.states(EnumSet.allOf(TestStates.class));
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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.config.common.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Iterator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanABuilder;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanB;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBeanBConfigurer;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter;
import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
/**
* Tests for generic javaconfig builder/configurer concepts.
*
* @author Janne Valkealahti
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class ComplexAnnotationConfigurationTests {
@Autowired
private ApplicationContext ctx;
@Test
public void testSimpleConfig() throws Exception {
assertNotNull(ctx);
assertTrue(ctx.containsBean("complexConfig"));
ComplexTestConfig config = ctx.getBean("complexConfig", ComplexTestConfig.class);
assertThat(config.complexData, notNullValue());
assertThat(config.complexData, is("complexData"));
assertThat(config.complexProperties, notNullValue());
assertThat(config.complexProperties.getProperty("complexKey1"), notNullValue());
assertThat(config.complexProperties.getProperty("complexKey1"), is("complexValue1"));
assertThat(config.complexBeanA, notNullValue());
assertThat(config.complexBeanA.dataA, notNullValue());
assertThat(config.complexBeanA.resources, notNullValue());
assertThat(config.complexBeanA.dataA, is("complexDataA"));
assertThat(config.complexBeanA.resources.size(), is(2));
Iterator<Resource> iterator = config.complexBeanA.resources.iterator();
String fileName1 = iterator.next().getFilename();
String fileName2 = iterator.next().getFilename();
String[] fileNames = new String[2];
fileNames[0] = fileName1.equals("complexResourceA1") ? fileName1 : fileName2;
fileNames[1] = fileName2.equals("complexResourceA2") ? fileName2 : fileName1;
assertThat(fileNames[0], is("complexResourceA1"));
assertThat(fileNames[1], is("complexResourceA2"));
assertTrue(ctx.containsBean("complexConfigData"));
assertTrue(ctx.containsBean("complexConfigBeanB"));
ComplexTestConfigBeanB beanB = ctx.getBean("complexConfigBeanB", ComplexTestConfigBeanB.class);
assertThat(beanB.dataB, is("complexDataB"));
assertThat(beanB.dataBB, is("complexDataBB"));
}
@Configuration
@EnableComplexTest
static class Config extends ComplexTestConfigurerAdapter {
@Override
public void configure(ComplexTestConfigBuilder config) throws Exception {
config
.withProperties()
.property("complexKey1", "complexValue1");
}
@Override
public void configure(ComplexTestConfigBeanABuilder beanA) throws Exception {
beanA
.withResources()
.resource("complexResourceA1")
.resource("complexResourceA2")
.and()
.setData("complexDataA");
}
@Override
public void configure(ComplexTestConfigBeanBConfigurer beanB) throws Exception {
beanB
.setData("complexDataB")
.setDataBB("complexDataBB")
.withResources().and();
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.config.common.annotation;
import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB;
public class DependencyBean {
private SimpleTestConfigBeanB beanB;
public SimpleTestConfigBeanB getBeanB() {
return beanB;
}
public void setBeanB(SimpleTestConfigBeanB beanB) {
this.beanB = beanB;
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.config.common.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfig;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigBuilder;
import org.springframework.statemachine.config.common.annotation.complex.ComplexTestConfigurerAdapter;
import org.springframework.statemachine.config.common.annotation.complex.EnableComplexTest;
import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest;
import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig;
import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder;
import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class MixedAnnotationConfigurationTests {
@Autowired
private ApplicationContext ctx;
@Test
public void testConfig() throws Exception {
assertNotNull(ctx);
assertTrue(ctx.containsBean("simpleConfig"));
SimpleTestConfig simpleConfig = ctx.getBean("simpleConfig", SimpleTestConfig.class);
assertThat(simpleConfig.simpleData, notNullValue());
assertThat(simpleConfig.simpleData, is("simpleData"));
assertThat(simpleConfig.simpleProperties, notNullValue());
assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), notNullValue());
assertThat(simpleConfig.simpleProperties.getProperty("simpleKey1"), is("simpleValue1"));
assertTrue(ctx.containsBean("complexConfig"));
ComplexTestConfig complexConfig = ctx.getBean("complexConfig", ComplexTestConfig.class);
assertThat(complexConfig.complexData, notNullValue());
assertThat(complexConfig.complexData, is("complexData"));
assertThat(complexConfig.complexProperties, notNullValue());
assertThat(complexConfig.complexProperties.getProperty("complexKey1"), notNullValue());
assertThat(complexConfig.complexProperties.getProperty("complexKey1"), is("complexValue1"));
assertThat(complexConfig.simpleTestConfig, notNullValue());
assertThat(complexConfig.simpleTestConfig.simpleData, notNullValue());
assertThat(complexConfig.simpleTestConfig.simpleData, is("simpleData"));
}
@Configuration
@EnableSimpleTest
static class SimpleConfig extends SimpleTestConfigurerAdapter {
@Override
public void configure(SimpleTestConfigBuilder config) throws Exception {
config
.withProperties()
.property("simpleKey1", "simpleValue1");
}
}
@Configuration
@EnableComplexTest
static class ComplexConfig extends ComplexTestConfigurerAdapter {
@Override
public void configure(ComplexTestConfigBuilder config) throws Exception {
config
.withProperties()
.property("complexKey1", "complexValue1");
}
}
}

Some files were not shown because too many files have changed in this diff Show More