/** * Returns the de-serialized id indicating the flow id of this session. */ String getFlowId() { if (flow == null) { return flowId; } else { return flow.getId(); } }
public String getCaption() { return "execution of '" + flow.getId() + "'"; }
public StateDefinition getStartState() { if (startState == null) { throw new IllegalStateException("No start state has been set for this flow ('" + getId() + "') -- flow builder configuration error?"); } return startState; }
/** * Set the start state for this flow to the state provided; any state may be the start state. * @param state the new start state * @throws IllegalArgumentException given state has not been added to this flow */ public void setStartState(State state) throws IllegalArgumentException { if (!states.contains(state)) { throw new IllegalArgumentException("State '" + state + "' is not a state of flow '" + getId() + "'"); } startState = state; }
public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(flow.getId()); out.writeObject(state != null ? state.getId() : null); out.writeObject(scope); out.writeObject(parent); }
/** * Return the <code>TransitionableState</code> with given <code>stateId</code>. * @param stateId id of the state to look up * @return the transitionable state * @throws IllegalArgumentException if the identified state cannot be found * @throws ClassCastException when the identified state is not transitionable */ public TransitionableState getTransitionableState(String stateId) throws IllegalArgumentException, ClassCastException { State state = getStateInstance(stateId); if (state != null && !(state instanceof TransitionableState)) { throw new ClassCastException("The state '" + stateId + "' of flow '" + getId() + "' must be transitionable"); } return (TransitionableState) state; }
@SuppressWarnings("unchecked") public MutableAttributeMap<Object> getViewScope() throws IllegalStateException { if (state == null) { throw new IllegalStateException("The current state of this flow '" + flow.getId() + "' is [null] - cannot access view scope"); } if (!state.isViewState()) { throw new IllegalStateException("The current state '" + state.getId() + "' of this flow '" + flow.getId() + "' is not a view state - view scope not accessible"); } return (MutableAttributeMap<Object>) scope.get(VIEW_SCOPE_ATTRIBUTE); }
@SuppressWarnings("unchecked") public MutableAttributeMap<Object> getViewScope() throws IllegalStateException { if (state == null) { throw new IllegalStateException("The current state of this flow '" + definition.getId() + "' is [null] - cannot access view scope"); } if (!state.isViewState()) { throw new IllegalStateException("The current state '" + state.getId() + "' of this flow '" + definition.getId() + "' is not a view state - view scope not accessible"); } return (MutableAttributeMap<Object>) scope.get(VIEW_MAP_ATTRIBUTE); }
/** * Creates a new exception. * @param state the action state * @param resultEvent the action result event */ public NoMatchingActionResultTransitionException(ActionState state, Event resultEvent) { super(state.getFlow().getId(), state.getId(), resultEvent, "Cannot find a transition matching an action result event; continuing with next action..."); } }
/** * Lookup the identified state instance of this flow. * @param stateId the state id * @return the state * @throws IllegalArgumentException if the identified state cannot be found */ public State getStateInstance(String stateId) throws IllegalArgumentException { if (!StringUtils.hasText(stateId)) { throw new IllegalArgumentException("The specified stateId is invalid: state identifiers must be non-blank"); } for (State state : states) { if (state.getId().equals(stateId)) { return state; } } throw new IllegalArgumentException("Cannot find state with id '" + stateId + "' in flow '" + getId() + "' -- " + "Known state ids are '" + StylerUtils.style(getStateIds()) + "'"); }
/** * Add given state definition to this flow definition. Marked protected, as this method is to be called by the * (privileged) state definition classes themselves during state construction as part of a FlowBuilder invocation. * @param state the state to add * @throws IllegalArgumentException when the state cannot be added to the flow; for instance if another state shares * the same id as the one provided or if given state already belongs to another flow */ protected void add(State state) throws IllegalArgumentException { if (this != state.getFlow() && state.getFlow() != null) { throw new IllegalArgumentException("State " + state + " cannot be added to this flow '" + getId() + "' -- it already belongs to a different flow: '" + state.getFlow().getId() + "'"); } if (this.states.contains(state) || this.containsState(state.getId())) { throw new IllegalArgumentException("This flow '" + getId() + "' already contains a state with id '" + state.getId() + "' -- state ids must be locally unique to the flow definition; " + "existing state-ids of this flow include: " + StylerUtils.style(getStateIds())); } boolean firstAdd = states.isEmpty(); states.add(state); if (firstAdd) { setStartState(state); } }
/** * Specialization of State's <code>doEnter</code> template method that executes behaviour specific to this state * type in polymorphic fashion. * <p> * Entering this state, creates the subflow input map and spawns the subflow in the current flow execution. * @param context the control context for the currently executing flow, used by this state to manipulate the flow * execution * @throws FlowExecutionException if an exception occurs in this state */ protected void doEnter(RequestControlContext context) throws FlowExecutionException { MutableAttributeMap<Object> flowInput; if (subflowAttributeMapper != null) { flowInput = subflowAttributeMapper.createSubflowInput(context); } else { flowInput = new LocalAttributeMap<>(); } Flow subflow = (Flow) this.subflow.getValue(context); if (logger.isDebugEnabled()) { logger.debug("Calling subflow '" + subflow.getId() + "' with input " + flowInput); } context.start(subflow, flowInput); }
/** * Enter this state in the provided flow control context. This implementation just calls the * {@link #doEnter(RequestControlContext)} hook method, which should be implemented by subclasses, after executing * the entry actions. * @param context the control context for the currently executing flow, used by this state to manipulate the flow * execution * @throws FlowExecutionException if an exception occurs in this state */ public final void enter(RequestControlContext context) throws FlowExecutionException { if (logger.isDebugEnabled()) { logger.debug("Entering state '" + getId() + "' of flow '" + getFlow().getId() + "'"); } context.setCurrentState(this); doPreEntryActions(context); entryActionList.execute(context); doEnter(context); }
private FlowExecutionException wrap(Exception e) { if (isActive()) { FlowSession session = getActiveSession(); String flowId = session.getDefinition().getId(); String stateId = session.getState() != null ? session.getState().getId() : null; return new FlowExecutionException(flowId, stateId, "Exception thrown in state '" + stateId + "' of flow '" + flowId + "'", e); } else { return new FlowExecutionException(flow.getId(), null, "Exception thrown within inactive flow '" + flow.getId() + "'", e); } }
/** * Inform this flow definition that an execution session of itself has ended. As a result, the flow will do the * following: * <ol> * <li>Execute all registered end actions ({@link #getEndActionList()}).</li> * <li>Map data available in the flow execution control context into provided output map using a registered output * mapper ( {@link #setOutputMapper(Mapper)}).</li> * </ol> * @param context the flow execution control context * @param outcome the logical flow outcome that will be returned by the session, generally the id of the terminating * end state * @param output initial output produced by the session that is eligible for modification by this method * @throws FlowExecutionException when an exception occurs ending this flow */ public void end(RequestControlContext context, String outcome, MutableAttributeMap<?> output) throws FlowExecutionException { endActionList.execute(context); if (outputMapper != null) { MappingResults results = outputMapper.map(context, output); if (results != null && results.hasErrorResults()) { throw new FlowOutputMappingException(getId(), results); } } }
throw new NoMatchingTransitionException(getFlow().getId(), getId(), context.getCurrentEvent(), "No transition was matched on the event(s) signaled by the [" + executionCount + "] action(s) that executed in this action state '" + getId() + "' of flow '" + getFlow().getId() + "'; transitions must be defined to handle action result outcomes -- " + "possible flow configuration error? Note: the eventIds signaled were: '" + StylerUtils.style(eventIds)
/** * Get a transition in this state for given flow execution request context. Throws and exception when there is no * corresponding transition. * @throws NoMatchingTransitionException when a matching transition cannot be found */ public Transition getRequiredTransition(RequestContext context) throws NoMatchingTransitionException { Transition transition = getTransitionSet().getTransition(context); if (transition == null) { throw new NoMatchingTransitionException(getFlow().getId(), getId(), context.getCurrentEvent(), "No transition found on occurence of event '" + context.getCurrentEvent() + "' in state '" + getId() + "' of flow '" + getFlow().getId() + "' -- valid transitional criteria are " + StylerUtils.style(getTransitionSet().getTransitionCriterias()) + " -- likely programmer error, check the set of TransitionCriteria for this state"); } return transition; }
public String toString() { ToStringCreator creator = new ToStringCreator(this).append("id", getId()).append("flow", flow.getId()) .append("entryActionList", entryActionList).append("exceptionHandlerSet", exceptionHandlerSet); appendToString(creator); return creator.toString(); }
/** * Start a new session for this flow in its start state. This boils down to the following: * <ol> * <li>Create (setup) all registered flow variables ({@link #addVariable(FlowVariable)}) in flow scope.</li> * <li>Map provided input data into the flow. Typically data will be mapped into flow scope using the registered * input mapper ({@link #setInputMapper(Mapper)}).</li> * <li>Execute all registered start actions ( {@link #getStartActionList()}).</li> * <li>Enter the configured start state ({@link #setStartState(State)})</li> * </ol> * @param context the flow execution control context * @param input eligible input into the session * @throws FlowExecutionException when an exception occurs starting the flow */ public void start(RequestControlContext context, MutableAttributeMap<?> input) throws FlowExecutionException { assertStartStateSet(); createVariables(context); if (inputMapper != null) { MappingResults results = inputMapper.map(input, context); if (results != null && results.hasErrorResults()) { throw new FlowInputMappingException(getId(), results); } } startActionList.execute(context); startState.enter(context); }
public String toString() { if (!isActive()) { if (!hasStarted()) { return "[Not yet started " + getCaption() + "]"; } else { return "[Ended " + getCaption() + "]"; } } else { if (flow != null) { return new ToStringCreator(this).append("flow", flow.getId()).append("flowSessions", flowSessions) .toString(); } else { return "[Unhydrated execution of '" + getRootSession().getFlowId() + "']"; } } }