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.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020: import org.springframework.core.style.ToStringCreator;
021: import org.springframework.util.Assert;
022: import org.springframework.webflow.definition.FlowDefinition;
023: import org.springframework.webflow.definition.StateDefinition;
024: import org.springframework.webflow.execution.FlowExecutionException;
025: import org.springframework.webflow.execution.ViewSelection;
026:
027: /**
028: * A point in a flow where something happens. What happens is determined by a
029: * state's type. Standard types of states include action states, view states,
030: * subflow states, and end states.
031: * <p>
032: * Each state is associated with exactly one owning flow definition.
033: * Specializations of this class capture all the configuration information
034: * needed for a specific kind of state.
035: * <p>
036: * Subclasses should implement the <code>doEnter</code> method to execute the
037: * processing that should occur when this state is entered, acting on its
038: * configuration information. The ability to plugin custom state types that
039: * execute different behaviour polymorphically is the classic GoF state pattern.
040: * <p>
041: * Equality: Two states are equal if they have the same id and are part of the same flow.
042: *
043: * @see org.springframework.webflow.engine.TransitionableState
044: * @see org.springframework.webflow.engine.ActionState
045: * @see org.springframework.webflow.engine.ViewState
046: * @see org.springframework.webflow.engine.SubflowState
047: * @see org.springframework.webflow.engine.EndState
048: * @see org.springframework.webflow.engine.DecisionState
049: *
050: * @author Keith Donald
051: * @author Erwin Vervaet
052: */
053: public abstract class State extends AnnotatedObject implements
054: StateDefinition {
055:
056: /**
057: * Logger, for use in subclasses.
058: */
059: protected final Log logger = LogFactory.getLog(getClass());
060:
061: /**
062: * The state's owning flow.
063: */
064: private Flow flow;
065:
066: /**
067: * The state identifier, unique to the owning flow.
068: */
069: private String id;
070:
071: /**
072: * The list of actions to invoke when this state is entered.
073: */
074: private ActionList entryActionList = new ActionList();
075:
076: /**
077: * The set of exception handlers for this state.
078: */
079: private FlowExecutionExceptionHandlerSet exceptionHandlerSet = new FlowExecutionExceptionHandlerSet();
080:
081: /**
082: * Creates a state for the provided <code>flow</code> identified by the
083: * provided <code>id</code>. The id must be locally unique to the owning
084: * flow. The state will be automatically added to the flow.
085: * @param flow the owning flow
086: * @param id the state identifier (must be unique to the flow)
087: * @throws IllegalArgumentException if this state cannot be added to the
088: * flow, for instance when the provided id is not unique in the owning flow
089: * @see #getEntryActionList()
090: * @see #getExceptionHandlerSet()
091: */
092: protected State(Flow flow, String id)
093: throws IllegalArgumentException {
094: setId(id);
095: setFlow(flow);
096: }
097:
098: // implementing StateDefinition
099:
100: public FlowDefinition getOwner() {
101: return flow;
102: }
103:
104: public String getId() {
105: return id;
106: }
107:
108: // implementation specific
109:
110: /**
111: * Returns the owning flow.
112: */
113: public Flow getFlow() {
114: return flow;
115: }
116:
117: /**
118: * Set the owning flow.
119: * @throws IllegalArgumentException if this state cannot be added to the
120: * flow
121: */
122: private void setFlow(Flow flow) throws IllegalArgumentException {
123: Assert
124: .hasText(getId(),
125: "The id of the state should be set before adding the state to a flow");
126: Assert.notNull(flow, "The owning flow is required");
127: this .flow = flow;
128: flow.add(this );
129: }
130:
131: /**
132: * Set the state identifier, unique to the owning flow.
133: * @param id the state identifier
134: */
135: private void setId(String id) {
136: Assert.hasText(id, "This state must have a valid identifier");
137: this .id = id;
138: }
139:
140: /**
141: * Returns the list of actions executed by this state when it is entered.
142: * The returned list is mutable.
143: * @return the state entry action list
144: */
145: public ActionList getEntryActionList() {
146: return entryActionList;
147: }
148:
149: /**
150: * Returns a mutable set of exception handlers, allowing manipulation of how
151: * exceptions are handled when thrown within this state.
152: * <p>
153: * Exception handlers are invoked when an exception occurs when this state
154: * is entered, and can execute custom exception handling logic as well as
155: * select an error view to display.
156: * @return the state exception handler set
157: */
158: public FlowExecutionExceptionHandlerSet getExceptionHandlerSet() {
159: return exceptionHandlerSet;
160: }
161:
162: /**
163: * Returns a flag indicating if this state is the start state of its owning
164: * flow.
165: * @return true if the flow is the start state, false otherwise
166: */
167: public boolean isStartState() {
168: return flow.getStartState() == this ;
169: }
170:
171: // id and flow based equality
172:
173: public boolean equals(Object o) {
174: if (!(o instanceof State)) {
175: return false;
176: }
177: State other = (State) o;
178: return id.equals(other.id) && flow.equals(other.flow);
179: }
180:
181: public int hashCode() {
182: return id.hashCode() + flow.hashCode();
183: }
184:
185: // behavioral methods
186:
187: /**
188: * Enter this state in the provided flow control context. This
189: * implementation just calls the
190: * {@link #doEnter(RequestControlContext)} hook method, which should
191: * be implemented by subclasses, after executing the entry actions.
192: * @param context the control context for the currently executing flow, used
193: * by this state to manipulate the flow execution
194: * @return a view selection containing model and view information needed to
195: * render the results of the state processing
196: * @throws FlowExecutionException if an exception occurs in this state
197: */
198: public final ViewSelection enter(RequestControlContext context)
199: throws FlowExecutionException {
200: if (logger.isDebugEnabled()) {
201: logger.debug("Entering state '" + getId() + "' of flow '"
202: + getFlow().getId() + "'");
203: }
204: context.setCurrentState(this );
205: entryActionList.execute(context);
206: return doEnter(context);
207: }
208:
209: /**
210: * Hook method to execute custom behaviour as a result of entering this
211: * state. By implementing this method subclasses specialize the behaviour of
212: * the state.
213: * @param context the control context for the currently executing flow, used
214: * by this state to manipulate the flow execution
215: * @return a view selection containing model and view information needed to
216: * render the results of the state processing
217: * @throws FlowExecutionException if an exception occurs in this state
218: */
219: protected abstract ViewSelection doEnter(
220: RequestControlContext context)
221: throws FlowExecutionException;
222:
223: /**
224: * Handle an exception that occured in this state during the context of the
225: * current flow execution request.
226: * @param exception the exception that occured
227: * @param context the flow execution control context
228: * @return the selected error view, or <code>null</code> if no handler
229: * matched or returned a non-null view selection
230: */
231: public ViewSelection handleException(
232: FlowExecutionException exception,
233: RequestControlContext context) {
234: return getExceptionHandlerSet().handleException(exception,
235: context);
236: }
237:
238: public String toString() {
239: String flowName = (flow == null ? "<not set>" : flow.getId());
240: ToStringCreator creator = new ToStringCreator(this ).append(
241: "id", getId()).append("flow", flowName).append(
242: "entryActionList", entryActionList).append(
243: "exceptionHandlerSet", exceptionHandlerSet);
244: appendToString(creator);
245: return creator.toString();
246: }
247:
248: /**
249: * Subclasses may override this hook method to stringify their internal
250: * state. This default implementation does nothing.
251: * @param creator the toString creator, to stringify properties
252: */
253: protected void appendToString(ToStringCreator creator) {
254: }
255: }
|