/* * Copyright 2004-2012 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.webflow.engine.impl; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.core.collection.MutableAttributeMap; import org.springframework.webflow.definition.FlowDefinition; import org.springframework.webflow.definition.StateDefinition; import org.springframework.webflow.engine.Flow; import org.springframework.webflow.engine.State; import org.springframework.webflow.execution.FlowSession; /** * Implementation of the FlowSession interfaced used internally by the <code>FlowExecutionImpl</code>. This class is * closely coupled with <code>FlowExecutionImpl</code> and <code>RequestControlContextImpl</code>. The three classes * work together to form a complete flow execution implementation. * * @author Keith Donald * @author Erwin Vervaet */ class FlowSessionImpl implements FlowSession, Externalizable { private static final String VIEW_SCOPE_ATTRIBUTE = "viewScope"; private static final String EMBEDDED_MODE_ATTRIBUTE = "embeddedMode"; /** * The flow definition (a singleton). * <p> * Transient to support restoration by the {@link FlowExecutionImplFactory}. */ private transient Flow flow; /** * The current state of this flow session. * <p> * Transient to support restoration by the {@link FlowExecutionImplFactory}. */ private transient State state; /** * The session data model ("flow scope"). */ private MutableAttributeMap<Object> scope = new LocalAttributeMap<Object>(); /** * The parent session of this session (may be <code>null</code> if this is a root session.) */ private FlowSessionImpl parent; /** * Set so the transient {@link #flow} field can be restored by the {@link FlowExecutionImplFactory}. */ private String flowId; /** * Set so the transient {@link #state} field can be restored by the {@link FlowExecutionImplFactory}. */ private String stateId; /** * Default constructor required for externalizable serialization. Should NOT be called programmatically. */ public FlowSessionImpl() { } /** * Create a new flow session. * @param flow the flow definition associated with this flow session * @param parent this session's parent (may be null) */ public FlowSessionImpl(Flow flow, FlowSessionImpl parent) { Assert.notNull(flow, "The flow is required"); this.flow = flow; this.parent = parent; } // implementing FlowSession public FlowDefinition getDefinition() { return flow; } public StateDefinition getState() { return state; } public MutableAttributeMap<Object> getScope() { return scope; } @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); } public boolean isEmbeddedMode() { return (Boolean) scope.get(EMBEDDED_MODE_ATTRIBUTE, false); } public FlowSession getParent() { return parent; } public boolean isRoot() { return parent == null; } // public impl public void setCurrentState(State state) { if (this.state != null && this.state.isViewState()) { destroyViewScope(); } this.state = state; if (this.state.isViewState()) { initViewScope(); } } // custom serialization @SuppressWarnings("unchecked") public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { flowId = (String) in.readObject(); stateId = (String) in.readObject(); scope = (MutableAttributeMap<Object>) in.readObject(); parent = (FlowSessionImpl) in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(flow.getId()); out.writeObject(state != null ? state.getId() : null); out.writeObject(scope); out.writeObject(parent); } // package-private Flow getFlow() { return flow; } // package private setters used by FlowExecutionImplFactory for setting/updating internal state /** * Restores the definition of this flow session. * @param flow the flow sessions definition * @see FlowExecutionImplStateRestorer */ void setFlow(Flow flow) { Assert.notNull(flow, "The flow is required"); this.flow = flow; } /** * Set the current state of this flow session. * @param state the state that is currently active in this flow session * @see FlowExecutionImpl#setCurrentState(State) * @see FlowExecutionImplStateRestorer */ void setState(State state) { Assert.notNull(state, "The state is required"); Assert.isTrue(flow == state.getOwner(), "The state does not belong to the flow associated with this flow session"); this.state = state; } /** * Returns the de-serialized id indicating the flow id of this session. */ String getFlowId() { if (flow == null) { return flowId; } else { return flow.getId(); } } /** * Sets the de-serialized id indicating the flow id of this session. Used for testing only. * @param flowId the flow id */ void setFlowId(String flowId) { this.flowId = flowId; } /** * Returns the de-serialized id indicating the current state of this session. */ String getStateId() { if (state == null) { return stateId; } else { return state.getId(); } } /** * Sets the de-serialized id indicating the state of this session. Used for testing only. * @param stateId the state id */ void setStateId(String stateId) { this.stateId = stateId; } /** * Set a flow session attribute to indicate the current session should execute in embedded mode. * @see FlowSession#isEmbeddedMode() */ void setEmbeddedMode() { this.scope.put(EMBEDDED_MODE_ATTRIBUTE, true); } // internal helpers /** * Initialize the view scope data structure. */ private void initViewScope() { scope.put(VIEW_SCOPE_ATTRIBUTE, new LocalAttributeMap<Object>()); } /** * Destroy the view scope data structure. */ private void destroyViewScope() { scope.remove(VIEW_SCOPE_ATTRIBUTE); } public String toString() { if (flow != null) { return new ToStringCreator(this).append("flow", getFlowId()).append("state", getStateId()) .append("scope", scope).toString(); } else { return "[Unhydrated session '" + flowId + "' in state '" + stateId + "']"; } } }