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.binding.mapping.AttributeMapper;
019: import org.springframework.core.style.ToStringCreator;
020: import org.springframework.util.Assert;
021: import org.springframework.webflow.core.collection.LocalAttributeMap;
022: import org.springframework.webflow.execution.Event;
023: import org.springframework.webflow.execution.FlowExecutionException;
024: import org.springframework.webflow.execution.FlowSession;
025: import org.springframework.webflow.execution.RequestContext;
026: import org.springframework.webflow.execution.ViewSelection;
027:
028: /**
029: * A state that ends a flow when entered. More specifically, this state ends the
030: * active flow session of the active flow execution associated with the current
031: * request context.
032: * <p>
033: * If the ended session is the "root flow session" the entire flow execution
034: * ends, signaling the end of a logical conversation.
035: * <p>
036: * If the terminated session was acting as a subflow the flow execution
037: * continues and control is returned to the parent flow session. In that case,
038: * this state returns an ending result event the resuming parent flow is
039: * expected to respond to.
040: * <p>
041: * An end state may optionally be configured with the name of a view to render
042: * when entered. This view will be rendered if the end state terminates the
043: * entire flow execution as a kind of flow ending "confirmation page".
044: * <p>
045: * Note: if no <code>viewName</code> property is specified <b>and</b> this
046: * end state terminates the entire flow execution it is expected that some
047: * action has already written the response (or else a blank response will
048: * result). On the other hand, if no <code>viewName</code> is specified <b>and</b>
049: * this end state relinquishes control back to a parent flow, view selection
050: * responsibility falls on the parent flow.
051: *
052: * @see org.springframework.webflow.engine.ViewSelector
053: * @see org.springframework.webflow.engine.SubflowState
054: *
055: * @author Keith Donald
056: * @author Colin Sampaleanu
057: * @author Erwin Vervaet
058: */
059: public class EndState extends State {
060:
061: /**
062: * The optional view selector that will select a view to render if this end
063: * state terminates a root flow session.
064: */
065: private ViewSelector viewSelector = NullViewSelector.INSTANCE;
066:
067: /**
068: * Attribute mapper for mapping output attributes exposed by this end state
069: * when it is entered.
070: */
071: private AttributeMapper outputMapper;
072:
073: /**
074: * Create a new end state with no associated view.
075: * @param flow the owning flow
076: * @param id the state identifier (must be unique to the flow)
077: * @throws IllegalArgumentException when this state cannot be added to given
078: * flow, e.g. because the id is not unique
079: * @see State#State(Flow, String)
080: * @see #setViewSelector(ViewSelector)
081: * @see #setOutputMapper(AttributeMapper)
082: */
083: public EndState(Flow flow, String id)
084: throws IllegalArgumentException {
085: super (flow, id);
086: }
087:
088: /**
089: * Returns the strategy used to select the view to render in this end state
090: * if it terminates a root flow.
091: */
092: public ViewSelector getViewSelector() {
093: return viewSelector;
094: }
095:
096: /**
097: * Sets the strategy used to select the view to render when this end state
098: * is entered and terminates a root flow.
099: */
100: public void setViewSelector(ViewSelector viewSelector) {
101: Assert.notNull(viewSelector, "The view selector is required");
102: this .viewSelector = viewSelector;
103: }
104:
105: /**
106: * Returns the configured attribute mapper for mapping output attributes
107: * exposed by this end state when it is entered.
108: */
109: public AttributeMapper getOutputMapper() {
110: return outputMapper;
111: }
112:
113: /**
114: * Sets the attribute mapper to use for mapping output attributes exposed by
115: * this end state when it is entered.
116: */
117: public void setOutputMapper(AttributeMapper outputMapper) {
118: this .outputMapper = outputMapper;
119: }
120:
121: /**
122: * Specialization of State's <code>doEnter</code> template method that
123: * executes behaviour specific to this state type in polymorphic fashion.
124: * <p>
125: * This implementation pops the top (active) flow session off the execution
126: * stack, ending it, and resumes control in the parent flow (if neccessary).
127: * If the ended session is the root flow, a {@link ViewSelection} is
128: * returned.
129: * @param context the control context for the currently executing flow, used
130: * by this state to manipulate the flow execution
131: * @return a view selection signaling that control should be returned to the
132: * client and a view rendered
133: * @throws FlowExecutionException if an exception occurs in this state
134: */
135: protected ViewSelection doEnter(RequestControlContext context)
136: throws FlowExecutionException {
137: FlowSession activeSession = context.getFlowExecutionContext()
138: .getActiveSession();
139: if (activeSession.isRoot()) {
140: // entire flow execution is ending, return ending view if applicable
141: ViewSelection selectedView = viewSelector
142: .makeEntrySelection(context);
143: context.endActiveFlowSession(createSessionOutput(context));
144: return selectedView;
145: } else {
146: // there is a parent flow that will resume (this flow is a subflow)
147: LocalAttributeMap sessionOutput = createSessionOutput(context);
148: context.endActiveFlowSession(sessionOutput);
149: return context.signalEvent(new Event(this , getId(),
150: sessionOutput));
151: }
152: }
153:
154: /**
155: * Returns the subflow output map. This will invoke the output mapper (if any)
156: * to map data available in the flow execution request context into a newly
157: * created empty map.
158: */
159: protected LocalAttributeMap createSessionOutput(
160: RequestContext context) {
161: LocalAttributeMap outputMap = new LocalAttributeMap();
162: if (outputMapper != null) {
163: outputMapper.map(context, outputMap, null);
164: }
165: return outputMap;
166: }
167:
168: protected void appendToString(ToStringCreator creator) {
169: creator.append("viewSelector", viewSelector).append(
170: "outputMapper", outputMapper);
171: }
172: }
|