001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.webflow.engine;
017:
018: import org.springframework.core.style.ToStringCreator;
019: import org.springframework.util.Assert;
020: import org.springframework.webflow.core.collection.AttributeMap;
021: import org.springframework.webflow.core.collection.MutableAttributeMap;
022: import org.springframework.webflow.execution.FlowExecutionException;
023: import org.springframework.webflow.execution.RequestContext;
024: import org.springframework.webflow.execution.ViewSelection;
025:
026: /**
027: * A transitionable state that spawns a subflow when executed. When the subflow
028: * this state spawns ends, the ending result is used as grounds for a state
029: * transition out of this state.
030: * <p>
031: * A subflow state may be configured to map input data from its flow -- acting
032: * as the parent flow -- down to the subflow when the subflow is spawned. In
033: * addition, output data produced by the subflow may be mapped up to the parent
034: * flow when the subflow ends and the parent flow resumes. See the
035: * {@link FlowAttributeMapper} interface definition for more information on how
036: * to do this. The logic for ending a subflow is located in the {@link EndState}
037: * implementation.
038: *
039: * @see org.springframework.webflow.engine.FlowAttributeMapper
040: * @see org.springframework.webflow.engine.EndState
041: *
042: * @author Keith Donald
043: * @author Erwin Vervaet
044: */
045: public class SubflowState extends TransitionableState {
046:
047: /**
048: * The subflow that should be spawned when this subflow state is entered.
049: */
050: private Flow subflow;
051:
052: /**
053: * The attribute mapper that should map attributes from the parent flow down
054: * to the spawned subflow and visa versa.
055: */
056: private FlowAttributeMapper attributeMapper;
057:
058: /**
059: * Create a new subflow state.
060: * @param flow the owning flow
061: * @param id the state identifier (must be unique to the flow)
062: * @param subflow the subflow to spawn
063: * @throws IllegalArgumentException when this state cannot be added to given
064: * flow, e.g. because the id is not unique
065: * @see #setAttributeMapper(FlowAttributeMapper)
066: */
067: public SubflowState(Flow flow, String id, Flow subflow)
068: throws IllegalArgumentException {
069: super (flow, id);
070: setSubflow(subflow);
071: }
072:
073: /**
074: * Returns the subflow spawned by this state.
075: */
076: public Flow getSubflow() {
077: return subflow;
078: }
079:
080: /**
081: * Set the subflow that will be spawned by this state.
082: * @param subflow the subflow to spawn
083: */
084: private void setSubflow(Flow subflow) {
085: Assert
086: .notNull(subflow,
087: "A subflow state must have a subflow; the subflow is required");
088: this .subflow = subflow;
089: }
090:
091: /**
092: * Returns the attribute mapper used to map data between the parent and child
093: * flow, or null if no mapping is needed.
094: */
095: public FlowAttributeMapper getAttributeMapper() {
096: return attributeMapper;
097: }
098:
099: /**
100: * Set the attribute mapper used to map model data between the parent and
101: * child flow. Can be null if no mapping is needed.
102: */
103: public void setAttributeMapper(FlowAttributeMapper attributeMapper) {
104: this .attributeMapper = attributeMapper;
105: }
106:
107: /**
108: * Specialization of State's <code>doEnter</code> template method that
109: * executes behaviour specific to this state type in polymorphic fashion.
110: * <p>
111: * Entering this state, creates the subflow input map and spawns the subflow
112: * in the current flow execution.
113: * @param context the control context for the currently executing flow, used
114: * by this state to manipulate the flow execution
115: * @return a view selection containing model and view information needed to
116: * render the results of the state execution
117: * @throws FlowExecutionException if an exception occurs in this state
118: */
119: protected ViewSelection doEnter(RequestControlContext context)
120: throws FlowExecutionException {
121: if (logger.isDebugEnabled()) {
122: logger.debug("Spawning subflow '" + getSubflow().getId()
123: + "' within flow '" + getFlow().getId() + "'");
124: }
125: return context.start(getSubflow(), createSubflowInput(context));
126: }
127:
128: /**
129: * Create the input data map for the spawned subflow session. The returned
130: * map will be passed to {@link Flow#start(RequestControlContext, MutableAttributeMap)}.
131: */
132: protected MutableAttributeMap createSubflowInput(
133: RequestContext context) {
134: if (getAttributeMapper() != null) {
135: if (logger.isDebugEnabled()) {
136: logger
137: .debug("Messaging the configured attribute mapper to map attributes "
138: + "down to the spawned subflow for access within the subflow");
139: }
140: return getAttributeMapper().createFlowInput(context);
141: } else {
142: if (logger.isDebugEnabled()) {
143: logger
144: .debug("No attribute mapper configured for this subflow state '"
145: + getId()
146: + "' -- As a result, no attributes will be passed to the spawned subflow '"
147: + subflow.getId() + "'");
148: }
149: return null;
150: }
151: }
152:
153: /**
154: * Called on completion of the subflow to handle the subflow result event as
155: * determined by the end state reached by the subflow.
156: */
157: public ViewSelection onEvent(RequestControlContext context) {
158: mapSubflowOutput(context.getLastEvent().getAttributes(),
159: context);
160: return super .onEvent(context);
161: }
162:
163: /**
164: * Map the output data produced by the subflow back into the request context
165: * (typically flow scope).
166: */
167: private void mapSubflowOutput(AttributeMap subflowOutput,
168: RequestContext context) {
169: if (getAttributeMapper() != null) {
170: if (logger.isDebugEnabled()) {
171: logger
172: .debug("Messaging the configured attribute mapper to map subflow result attributes to the "
173: + "resuming parent flow -- It will have access to attributes passed up by the completed subflow");
174: }
175: attributeMapper.mapFlowOutput(subflowOutput, context);
176: } else {
177: if (logger.isDebugEnabled()) {
178: logger
179: .debug("No attribute mapper is configured for the resuming subflow state '"
180: + getId()
181: + "' -- As a result, no attributes of the ending flow will be passed to the resuming parent flow");
182: }
183: }
184: }
185:
186: protected void appendToString(ToStringCreator creator) {
187: creator.append("subflow", subflow.getId()).append(
188: "attributeMapper", attributeMapper);
189: super.appendToString(creator);
190: }
191: }
|