/* * Copyright 2015-2017 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.Collections; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanNameAware; import org.springframework.core.OrderComparator; import org.springframework.messaging.Message; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.event.StateMachineEventPublisher; import org.springframework.statemachine.listener.CompositeStateMachineListener; import org.springframework.statemachine.listener.StateMachineListener; import org.springframework.statemachine.monitor.CompositeStateMachineMonitor; import org.springframework.statemachine.processor.StateMachineHandlerCallHelper; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; import org.springframework.util.Assert; /** * Support and helper class for base state machine implementation. * * @author Janne Valkealahti * * @param <S> the type of state * @param <E> the type of event */ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSupport implements BeanNameAware { private static final Log log = LogFactory.getLog(StateMachineObjectSupport.class); private final CompositeStateMachineListener<S, E> stateListener = new CompositeStateMachineListener<S, E>(); private final CompositeStateMachineMonitor<S, E> stateMachineMonitor = new CompositeStateMachineMonitor<S, E>(); /** Context application event publisher if exist */ private volatile StateMachineEventPublisher stateMachineEventPublisher; /** Flag for application context events */ private boolean contextEventsEnabled = true; private final StateMachineInterceptorList<S, E> interceptors = new StateMachineInterceptorList<S, E>(); private String beanName; private volatile boolean handlersInitialized; private final StateMachineHandlerCallHelper<S, E> stateMachineHandlerCallHelper = new StateMachineHandlerCallHelper<S, E>(); @Override protected void doStart() { super.doStart(); if (!handlersInitialized) { try { stateMachineHandlerCallHelper.setBeanFactory(getBeanFactory()); stateMachineHandlerCallHelper.afterPropertiesSet(); } catch (Exception e) { log.error("Unable to initialize annotation handlers", e); } finally { handlersInitialized = true; } } } @Override public void setBeanName(String name) { beanName = name; } /** * Returns a bean name known to context per contract * with {@link BeanNameAware}. * * @return a bean name */ protected String getBeanName() { return beanName; } /** * Gets the state machine event publisher. * * @return the state machine event publisher */ protected StateMachineEventPublisher getStateMachineEventPublisher() { if(stateMachineEventPublisher == null && getBeanFactory() != null) { if(log.isTraceEnabled()) { log.trace("getting stateMachineEventPublisher service from bean factory " + getBeanFactory()); } stateMachineEventPublisher = StateMachineContextUtils.getEventPublisher(getBeanFactory()); } return stateMachineEventPublisher; } /** * Sets the state machine event publisher. * * @param stateMachineEventPublisher the new state machine event publisher */ public void setStateMachineEventPublisher(StateMachineEventPublisher stateMachineEventPublisher) { Assert.notNull(stateMachineEventPublisher, "StateMachineEventPublisher cannot be null"); this.stateMachineEventPublisher = stateMachineEventPublisher; } /** * Set if context application events are enabled. Events * are enabled by default. Set this to false if you don't * want state machine to send application context events. * * @param contextEventsEnabled the enabled flag */ public void setContextEventsEnabled(boolean contextEventsEnabled) { this.contextEventsEnabled = contextEventsEnabled; } protected CompositeStateMachineListener<S, E> getStateListener() { return stateListener; } protected CompositeStateMachineMonitor<S, E> getStateMachineMonitor() { return stateMachineMonitor; } protected void notifyStateChanged(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnStateChanged(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnStateChanged(stateContext.getStateMachine().getId(), stateContext); stateListener.stateChanged(stateContext.getSource(), stateContext.getTarget()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishStateChanged(this, stateContext.getSource(), stateContext.getTarget()); } } } catch (Throwable e) { log.warn("Error during notifyStateChanged", e); } } protected void notifyStateEntered(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnStateEntry(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnStateEntry(stateContext.getStateMachine().getId(), stateContext); stateListener.stateEntered(stateContext.getTarget()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishStateEntered(this, stateContext.getTarget()); } } } catch (Throwable e) { log.warn("Error during notifyStateEntered", e); } } protected void notifyStateExited(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnStateExit(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnStateExit(stateContext.getStateMachine().getId(), stateContext); stateListener.stateExited(stateContext.getSource()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishStateExited(this, stateContext.getSource()); } } } catch (Throwable e) { log.warn("Error during notifyStateExited", e); } } protected void notifyEventNotAccepted(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnEventNotAccepted(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnEventNotAccepted(stateContext.getStateMachine().getId(), stateContext); stateListener.eventNotAccepted(stateContext.getMessage()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishEventNotAccepted(this, stateContext.getMessage()); } } } catch (Throwable e) { log.warn("Error during notifyEventNotAccepted", e); } } protected void notifyTransitionStart(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnTransitionStart(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnTransitionStart(stateContext.getStateMachine().getId(), stateContext); stateListener.transitionStarted(stateContext.getTransition()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishTransitionStart(this, stateContext.getTransition()); } } } catch (Throwable e) { log.warn("Error during notifyTransitionStart", e); } } protected void notifyTransition(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnTransition(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnTransition(stateContext.getStateMachine().getId(), stateContext); stateListener.transition(stateContext.getTransition()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishTransition(this, stateContext.getTransition()); } } } catch (Throwable e) { log.warn("Error during notifyTransition", e); } } protected void notifyTransitionEnd(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnTransitionEnd(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnTransitionEnd(stateContext.getStateMachine().getId(), stateContext); stateListener.transitionEnded(stateContext.getTransition()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishTransitionEnd(this, stateContext.getTransition()); } } } catch (Throwable e) { log.warn("Error during notifyTransitionEnd", e); } } protected void notifyStateMachineStarted(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnStateMachineStart(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnStateMachineStart(stateContext.getStateMachine().getId(), stateContext); stateListener.stateMachineStarted(stateContext.getStateMachine()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishStateMachineStart(this, stateContext.getStateMachine()); } } } catch (Throwable e) { log.warn("Error during notifyStateMachineStarted", e); } } protected void notifyStateMachineStopped(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnStateMachineStop(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnStateMachineStop(stateContext.getStateMachine().getId(), stateContext); stateListener.stateMachineStopped(stateContext.getStateMachine()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishStateMachineStop(this, stateContext.getStateMachine()); } } } catch (Throwable e) { log.warn("Error during notifyStateMachineStopped", e); } } protected void notifyStateMachineError(StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnStateMachineError(getBeanName(), stateContext); stateMachineHandlerCallHelper.callOnStateMachineError(stateContext.getStateMachine().getId(), stateContext); stateListener.stateMachineError(stateContext.getStateMachine(), stateContext.getException()); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishStateMachineError(this, stateContext.getStateMachine(), stateContext.getException()); } } } catch (Throwable e) { log.warn("Error during notifyStateMachineError", e); } } protected void notifyExtendedStateChanged(Object key, Object value, StateContext<S, E> stateContext) { try { stateMachineHandlerCallHelper.callOnExtendedStateChanged(getBeanName(), key, value, stateContext); stateMachineHandlerCallHelper.callOnExtendedStateChanged(stateContext.getStateMachine().getId(), key, value, stateContext); stateListener.extendedStateChanged(key, value); stateListener.stateContext(stateContext); if (contextEventsEnabled) { StateMachineEventPublisher eventPublisher = getStateMachineEventPublisher(); if (eventPublisher != null) { eventPublisher.publishExtendedStateChanged(this, key, value); } } } catch (Throwable e) { log.warn("Error during notifyExtendedStateChanged", e); } } protected void notifyTransitionMonitor(StateMachine<S, E> stateMachine, Transition<S, E> transition, long duration) { try { stateMachineMonitor.transition(stateMachine, transition, duration); } catch (Exception e) { log.warn("Error during notifyTransitionMonitor", e); } } protected void notifyActionMonitor(StateMachine<S, E> stateMachine, Action<S, E> action, long duration) { try { stateMachineMonitor.action(stateMachine, action, duration); } catch (Exception e) { log.warn("Error during notifyTransitionMonitor", e); } } protected void stateChangedInRelay() { // TODO: this is a temporary tweak to know when state is // changed in a submachine/regions order to give // state machine a change to request executor login again // which is needed when we use multiple thread. with multiple // threads submachines may do their stuff after thread handling // main machine has already finished its execution logic, thus // re-scheduling is needed. } protected StateMachineInterceptorList<S, E> getStateMachineInterceptors() { return interceptors; } protected void setStateMachineInterceptors(List<StateMachineInterceptor<S,E>> interceptors) { Collections.sort(interceptors, new OrderComparator()); this.interceptors.set(interceptors); } /** * This class is used to relay listener events from a submachines which works * as its own listener context. User only connects to main root machine and * expects to get events for all machines from there. */ protected class StateMachineListenerRelay implements StateMachineListener<S,E> { @Override public void stateChanged(State<S, E> from, State<S, E> to) { stateListener.stateChanged(from, to); stateChangedInRelay(); } @Override public void stateEntered(State<S, E> state) { stateListener.stateEntered(state); } @Override public void stateExited(State<S, E> state) { stateListener.stateExited(state); } @Override public void eventNotAccepted(Message<E> event) { stateListener.eventNotAccepted(event); } @Override public void transition(Transition<S, E> transition) { stateListener.transition(transition); } @Override public void transitionStarted(Transition<S, E> transition) { stateListener.transitionStarted(transition); } @Override public void transitionEnded(Transition<S, E> transition) { stateListener.transitionEnded(transition); } @Override public void stateMachineStarted(StateMachine<S, E> stateMachine) { stateListener.stateMachineStarted(stateMachine); } @Override public void stateMachineStopped(StateMachine<S, E> stateMachine) { stateListener.stateMachineStopped(stateMachine); } @Override public void stateMachineError(StateMachine<S, E> stateMachine, Exception exception) { stateListener.stateMachineError(stateMachine, exception); } @Override public void extendedStateChanged(Object key, Object value) { stateListener.extendedStateChanged(key, value); } @Override public void stateContext(StateContext<S, E> stateContext) { stateListener.stateContext(stateContext); } } }