KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > webflow > engine > impl > FlowExecutionImpl


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.webflow.engine.impl;
17
18 import java.io.Externalizable JavaDoc;
19 import java.io.IOException JavaDoc;
20 import java.io.ObjectInput JavaDoc;
21 import java.io.ObjectOutput JavaDoc;
22 import java.util.LinkedList JavaDoc;
23 import java.util.ListIterator JavaDoc;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.springframework.core.style.ToStringCreator;
28 import org.springframework.util.Assert;
29 import org.springframework.webflow.context.ExternalContext;
30 import org.springframework.webflow.core.collection.AttributeMap;
31 import org.springframework.webflow.core.collection.CollectionUtils;
32 import org.springframework.webflow.core.collection.LocalAttributeMap;
33 import org.springframework.webflow.core.collection.MutableAttributeMap;
34 import org.springframework.webflow.definition.FlowDefinition;
35 import org.springframework.webflow.engine.Flow;
36 import org.springframework.webflow.engine.RequestControlContext;
37 import org.springframework.webflow.engine.State;
38 import org.springframework.webflow.engine.ViewState;
39 import org.springframework.webflow.execution.Event;
40 import org.springframework.webflow.execution.FlowExecution;
41 import org.springframework.webflow.execution.FlowExecutionException;
42 import org.springframework.webflow.execution.FlowExecutionListener;
43 import org.springframework.webflow.execution.FlowSession;
44 import org.springframework.webflow.execution.FlowSessionStatus;
45 import org.springframework.webflow.execution.ViewSelection;
46
47 /**
48  * Default implementation of FlowExecution that uses a stack-based data
49  * structure to manage spawned flow sessions. This class is closely coupled with
50  * package-private <code>FlowSessionImpl</code> and
51  * <code>RequestControlContextImpl</code>. The three classes work together to
52  * form a complete flow execution implementation based on a finite state
53  * machine.
54  * <p>
55  * This implementation of FlowExecution is serializable so it can be safely
56  * stored in an HTTP session or other persistent store such as a file, database,
57  * or client-side form field. Once deserialized, the
58  * {@link FlowExecutionImplStateRestorer} strategy is expected to be used to
59  * restore the execution to a usable state.
60  *
61  * @see FlowExecutionImplFactory
62  * @see FlowExecutionImplStateRestorer
63  *
64  * @author Keith Donald
65  * @author Erwin Vervaet
66  * @author Ben Hale
67  */

68 public class FlowExecutionImpl implements FlowExecution, Externalizable JavaDoc {
69
70     private static final Log logger = LogFactory.getLog(FlowExecutionImpl.class);
71
72     /**
73      * The execution's root flow; the top level flow that acts as the starting
74      * point for this flow execution.
75      * <p>
76      * Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
77      */

78     private transient Flow flow;
79
80     /**
81      * The stack of active, currently executing flow sessions. As subflows are
82      * spawned, they are pushed onto the stack. As they end, they are popped off
83      * the stack.
84      */

85     private LinkedList JavaDoc flowSessions;
86
87     /**
88      * A thread-safe listener list, holding listeners monitoring the lifecycle
89      * of this flow execution.
90      * <p>
91      * Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
92      */

93     private transient FlowExecutionListeners listeners;
94
95     /**
96      * A data structure for attributes shared by all flow sessions.
97      * <p>
98      * Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
99      */

100     private transient MutableAttributeMap conversationScope;
101
102     /**
103      * A data structure for runtime system execution attributes.
104      * <p>
105      * Transient to support restoration by the {@link FlowExecutionImplStateRestorer}.
106      */

107     private transient AttributeMap attributes;
108
109     /**
110      * Set so the transient {@link #flow} field can be restored by the
111      * {@link FlowExecutionImplStateRestorer}.
112      */

113     private String JavaDoc flowId;
114
115     /**
116      * Default constructor required for externalizable serialization. Should NOT
117      * be called programmatically.
118      */

119     public FlowExecutionImpl() {
120     }
121
122     /**
123      * Create a new flow execution executing the provided flow. This constructor
124      * is mainly used for testing.
125      * @param flow the root flow of this flow execution
126      */

127     public FlowExecutionImpl(Flow flow) {
128         this(flow, new FlowExecutionListener[0], null);
129     }
130
131     /**
132      * Create a new flow execution executing the provided flow.
133      * @param flow the root flow of this flow execution
134      * @param listeners the listeners interested in flow execution lifecycle
135      * events
136      * @param attributes flow execution system attributes
137      */

138     public FlowExecutionImpl(Flow flow, FlowExecutionListener[] listeners, AttributeMap attributes) {
139         setFlow(flow);
140         this.flowSessions = new LinkedList JavaDoc();
141         this.listeners = new FlowExecutionListeners(listeners);
142         this.attributes = (attributes != null ? attributes : CollectionUtils.EMPTY_ATTRIBUTE_MAP);
143         this.conversationScope = new LocalAttributeMap();
144         if (logger.isDebugEnabled()) {
145             logger.debug("Created new execution of flow '" + flow.getId() + "'");
146         }
147     }
148
149     public String JavaDoc getCaption() {
150         return "execution of '" + flowId + "'";
151     }
152
153     // implementing FlowExecutionContext
154

155     public FlowDefinition getDefinition() {
156         return flow;
157     }
158
159     public boolean isActive() {
160         return !flowSessions.isEmpty();
161     }
162
163     public FlowSession getActiveSession() {
164         return getActiveSessionInternal();
165     }
166
167     public MutableAttributeMap getConversationScope() {
168         return conversationScope;
169     }
170
171     public AttributeMap getAttributes() {
172         return attributes;
173     }
174
175     // methods implementing FlowExecution
176

177     public ViewSelection start(MutableAttributeMap input, ExternalContext externalContext)
178             throws FlowExecutionException {
179         Assert.state(!isActive(),
180                 "This flow is already executing -- you cannot call 'start()' more than once");
181         RequestControlContext context = createControlContext(externalContext);
182         getListeners().fireRequestSubmitted(context);
183         try {
184             try {
185                 // launch a flow session for the root flow
186
ViewSelection selectedView = context.start(flow, input);
187                 return pause(context, selectedView);
188             }
189             catch (FlowExecutionException e) {
190                 return pause(context, handleException(e, context));
191             }
192         }
193         finally {
194             getListeners().fireRequestProcessed(context);
195         }
196     }
197
198     public ViewSelection signalEvent(String JavaDoc eventId, ExternalContext externalContext) throws FlowExecutionException {
199         assertActive();
200         if (logger.isDebugEnabled()) {
201             logger.debug("Resuming this execution on user event '" + eventId + "'");
202         }
203         RequestControlContext context = createControlContext(externalContext);
204         context.getFlashScope().clear();
205         getListeners().fireRequestSubmitted(context);
206         try {
207             try {
208                 resume(context);
209                 Event event =
210                     new Event(externalContext, eventId, externalContext.getRequestParameterMap().asAttributeMap());
211                 ViewSelection selectedView = context.signalEvent(event);
212                 return pause(context, selectedView);
213             }
214             catch (FlowExecutionException e) {
215                 return pause(context, handleException(e, context));
216             }
217         }
218         finally {
219             getListeners().fireRequestProcessed(context);
220         }
221     }
222
223     public ViewSelection refresh(ExternalContext externalContext) throws FlowExecutionException {
224         assertActive();
225         if (logger.isDebugEnabled()) {
226             logger.debug("Resuming this execution for refresh");
227         }
228         RequestControlContext context = createControlContext(externalContext);
229         getListeners().fireRequestSubmitted(context);
230         try {
231             try {
232                 resume(context);
233                 State currentState = getCurrentState();
234                 if (!(currentState instanceof ViewState)) {
235                     throw new IllegalStateException JavaDoc("Current state is not a view state - cannot refresh; "
236                             + "perhaps an unhandled exception occured in another state?");
237                 }
238                 ViewSelection selectedView = ((ViewState)currentState).refresh(context);
239                 return pause(context, selectedView);
240             }
241             catch (FlowExecutionException e) {
242                 return pause(context, handleException(e, context));
243             }
244         }
245         finally {
246             getListeners().fireRequestProcessed(context);
247         }
248     }
249
250     /**
251      * Returns the listener list.
252      * @return the attached execution listeners.
253      */

254     FlowExecutionListeners getListeners() {
255         return listeners;
256     }
257
258     /**
259      * Resume this flow execution.
260      * @param context the state request context
261      */

262     protected void resume(RequestControlContext context) {
263         getActiveSessionInternal().setStatus(FlowSessionStatus.ACTIVE);
264         getListeners().fireResumed(context);
265     }
266
267     /**
268      * Pause this flow execution.
269      * @param context the request control context
270      * @param selectedView the initial selected view to render
271      * @return the selected view to render
272      */

273     protected ViewSelection pause(RequestControlContext context, ViewSelection selectedView) {
274         if (!isActive()) {
275             // view selected by an end state
276
return selectedView;
277         }
278         getActiveSessionInternal().setStatus(FlowSessionStatus.PAUSED);
279         getListeners().firePaused(context, selectedView);
280         if (logger.isDebugEnabled()) {
281             if (selectedView != null) {
282                 logger.debug("Paused to render " + selectedView + " and wait for user input");
283             }
284             else {
285                 logger.debug("Paused to wait for user input");
286             }
287         }
288         return selectedView;
289     }
290
291     /**
292      * Handles an exception that occured performing an operation on this flow
293      * execution. First trys the set of exception handlers associated with the
294      * offending state, then the handlers at the flow level.
295      * @param exception the exception that occured
296      * @param context the request control context the exception occured in
297      * @return the selected error view, never null
298      * @throws FlowExecutionException rethrows the exception if it was not handled
299      * at the state or flow level
300      */

301     protected ViewSelection handleException(FlowExecutionException exception, RequestControlContext context)
302             throws FlowExecutionException {
303         getListeners().fireExceptionThrown(context, exception);
304         if (logger.isDebugEnabled()) {
305             logger.debug("Attempting to handle [" + exception + "]");
306         }
307         // the state could be null if the flow was attempting a start operation
308
ViewSelection selectedView = tryStateHandlers(exception, context);
309         if (selectedView != null) {
310             return selectedView;
311         }
312         selectedView = tryFlowHandlers(exception, context);
313         if (selectedView != null) {
314             return selectedView;
315         }
316         if (logger.isDebugEnabled()) {
317             logger.debug("Rethrowing unhandled flow execution exception");
318         }
319         throw exception;
320     }
321
322     /**
323      * Try to handle given exception using execution exception handlers registered
324      * at the state level. Returns null if no handler handled the exception.
325      */

326     private ViewSelection tryStateHandlers(FlowExecutionException exception, RequestControlContext context) {
327         ViewSelection selectedView = null;
328         if (exception.getStateId() != null) {
329             selectedView = getActiveFlow().getStateInstance(exception.getStateId()).handleException(exception, context);
330             if (selectedView != null) {
331                 if (logger.isDebugEnabled()) {
332                     logger.debug("State '" + exception.getStateId() + "' handled exception");
333                 }
334             }
335         }
336         return selectedView;
337     }
338
339     /**
340      * Try to handle given exception using execution exception handlers registered
341      * at the flow level. Returns null if no handler handled the exception.
342      */

343     private ViewSelection tryFlowHandlers(FlowExecutionException exception, RequestControlContext context) {
344         ViewSelection selectedView = getActiveFlow().handleException(exception, context);
345         if (selectedView != null) {
346             if (logger.isDebugEnabled()) {
347                 logger.debug("Flow '" + exception.getFlowId() + "' handled exception");
348             }
349         }
350         return selectedView;
351     }
352
353     // internal helpers
354

355     /**
356      * Create a flow execution control context.
357      * @param externalContext the external context triggering this request
358      */

359     protected RequestControlContext createControlContext(ExternalContext externalContext) {
360         return new RequestControlContextImpl(this, externalContext);
361     }
362
363     /**
364      * Returns the currently active flow session.
365      * @throws IllegalStateException this execution is not active
366      */

367     FlowSessionImpl getActiveSessionInternal() throws IllegalStateException JavaDoc {
368         assertActive();
369         return (FlowSessionImpl)flowSessions.getLast();
370     }
371
372     /**
373      * Set the state that is currently active in this flow execution.
374      * @param newState the new current state
375      */

376     protected void setCurrentState(State newState) {
377         getActiveSessionInternal().setState(newState);
378     }
379
380     /**
381      * Activate a new <code>FlowSession</code> for the flow definition.
382      * Pushes the new flow session onto the stack.
383      * @param flow the flow definition
384      * @return the new flow session
385      */

386     protected FlowSession activateSession(Flow flow) {
387         FlowSessionImpl session;
388         if (!flowSessions.isEmpty()) {
389             FlowSessionImpl parent = getActiveSessionInternal();
390             parent.setStatus(FlowSessionStatus.SUSPENDED);
391             session = createFlowSession(flow, parent);
392         }
393         else {
394             session = createFlowSession(flow, null);
395         }
396         flowSessions.add(session);
397         session.setStatus(FlowSessionStatus.STARTING);
398         if (logger.isDebugEnabled()) {
399             logger.debug("Starting " + session);
400         }
401         return session;
402     }
403
404     /**
405      * Create a new flow session object. Subclasses can override this to return
406      * a special implementation if required.
407      * @param flow the flow that should be associated with the flow session
408      * @param parent the flow session that should be the parent of the newly
409      * created flow session (may be null)
410      * @return the newly created flow session
411      */

412     FlowSessionImpl createFlowSession(Flow flow, FlowSessionImpl parent) {
413         return new FlowSessionImpl(flow, parent);
414     }
415
416     /**
417      * End the active flow session, popping it of the stack.
418      * @return the ended session
419      */

420     public FlowSession endActiveFlowSession() {
421         FlowSessionImpl endingSession = (FlowSessionImpl)flowSessions.removeLast();
422         endingSession.setStatus(FlowSessionStatus.ENDED);
423         if (!flowSessions.isEmpty()) {
424             if (logger.isDebugEnabled()) {
425                 logger.debug("Resuming session '" + getActiveSessionInternal().getDefinition().getId() + "' in state '"
426                         + getActiveSessionInternal().getState().getId() + "'");
427             }
428             getActiveSessionInternal().setStatus(FlowSessionStatus.ACTIVE);
429         }
430         else {
431             if (logger.isDebugEnabled()) {
432                 logger.debug("[Ended] - this execution is now inactive");
433             }
434         }
435         return endingSession;
436     }
437
438     /**
439      * Make sure that this flow execution is active and throw an exception if it's
440      * not.
441      */

442     private void assertActive() throws IllegalStateException JavaDoc {
443         if (!isActive()) {
444             throw new IllegalStateException JavaDoc(
445                     "This flow execution is not active, it has either ended or has never been started.");
446         }
447     }
448
449     /**
450      * Returns the currently active flow.
451      */

452     private Flow getActiveFlow() {
453         return (Flow)getActiveSessionInternal().getDefinition();
454     }
455
456     /**
457      * Returns the current state of this flow execution.
458      */

459     private State getCurrentState() {
460         return (State)getActiveSessionInternal().getState();
461     }
462
463     // custom serialization (implementation of Externalizable for optimized
464
// storage)
465

466     public void readExternal(ObjectInput JavaDoc in) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
467         flowId = (String JavaDoc)in.readObject();
468         flowSessions = (LinkedList JavaDoc)in.readObject();
469     }
470
471     public void writeExternal(ObjectOutput JavaDoc out) throws IOException JavaDoc {
472         out.writeObject(flowId);
473         out.writeObject(flowSessions);
474     }
475
476     public String JavaDoc toString() {
477         if (!isActive()) {
478             return "[Inactive " + getCaption() + "]";
479         }
480         else {
481             if (flow != null) {
482                 return new ToStringCreator(this).append("flow", flow.getId()).append("flowSessions", flowSessions)
483                         .toString();
484             }
485             else {
486                 return "[Unhydrated " + getCaption() + "]";
487             }
488         }
489     }
490
491     // package private setters for restoring transient state
492
// used by FlowExecutionImplStateRestorer
493

494     /**
495      * Restore the flow definition of this flow execution.
496      */

497     void setFlow(Flow flow) {
498         Assert.notNull(flow, "The root flow definition is required");
499         this.flow = flow;
500         this.flowId = flow.getId();
501     }
502
503     /**
504      * Restore the listeners of this flow execution.
505      */

506     void setListeners(FlowExecutionListeners listeners) {
507         Assert.notNull(listeners, "The execution listener list is required");
508         this.listeners = listeners;
509     }
510
511     /**
512      * Restore the execution attributes of this flow execution.
513      */

514     void setAttributes(AttributeMap attributes) {
515         Assert.notNull(conversationScope, "The execution attribute map is required");
516         this.attributes = attributes;
517     }
518
519     /**
520      * Restore conversation scope for this flow execution.
521      */

522     void setConversationScope(MutableAttributeMap conversationScope) {
523         Assert.notNull(conversationScope, "The conversation scope map is required");
524         this.conversationScope = conversationScope;
525     }
526
527     /**
528      * Returns the flow definition id of this flow execution.
529      */

530     String JavaDoc getFlowId() {
531         return flowId;
532     }
533
534     /**
535      * Returns the list of flow session maintained by this flow execution.
536      */

537     LinkedList JavaDoc getFlowSessions() {
538         return flowSessions;
539     }
540     
541     /**
542      * Are there any flow sessions in this flow execution?
543      */

544     boolean hasSessions() {
545         return !flowSessions.isEmpty();
546     }
547
548     /**
549      * Are there any sessions for sub flows in this flow execution?
550      */

551     boolean hasSubflowSessions() {
552         return flowSessions.size() > 1;
553     }
554
555     /**
556      * Returns the flow session for the root flow of this flow execution.
557      */

558     FlowSessionImpl getRootSession() {
559         return (FlowSessionImpl)flowSessions.getFirst();
560     }
561
562     /**
563      * Returns an iterator looping over the subflow sessions
564      * in this flow execution.
565      */

566     ListIterator JavaDoc getSubflowSessionIterator() {
567         return flowSessions.listIterator(1);
568     }
569
570 }
Popular Tags