1 16 package org.springframework.webflow.engine.impl; 17 18 import java.io.Externalizable ; 19 import java.io.IOException ; 20 import java.io.ObjectInput ; 21 import java.io.ObjectOutput ; 22 import java.util.LinkedList ; 23 import java.util.ListIterator ; 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 68 public class FlowExecutionImpl implements FlowExecution, Externalizable { 69 70 private static final Log logger = LogFactory.getLog(FlowExecutionImpl.class); 71 72 78 private transient Flow flow; 79 80 85 private LinkedList flowSessions; 86 87 93 private transient FlowExecutionListeners listeners; 94 95 100 private transient MutableAttributeMap conversationScope; 101 102 107 private transient AttributeMap attributes; 108 109 113 private String flowId; 114 115 119 public FlowExecutionImpl() { 120 } 121 122 127 public FlowExecutionImpl(Flow flow) { 128 this(flow, new FlowExecutionListener[0], null); 129 } 130 131 138 public FlowExecutionImpl(Flow flow, FlowExecutionListener[] listeners, AttributeMap attributes) { 139 setFlow(flow); 140 this.flowSessions = new LinkedList (); 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 getCaption() { 150 return "execution of '" + flowId + "'"; 151 } 152 153 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 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 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 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 ("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 254 FlowExecutionListeners getListeners() { 255 return listeners; 256 } 257 258 262 protected void resume(RequestControlContext context) { 263 getActiveSessionInternal().setStatus(FlowSessionStatus.ACTIVE); 264 getListeners().fireResumed(context); 265 } 266 267 273 protected ViewSelection pause(RequestControlContext context, ViewSelection selectedView) { 274 if (!isActive()) { 275 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 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 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 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 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 355 359 protected RequestControlContext createControlContext(ExternalContext externalContext) { 360 return new RequestControlContextImpl(this, externalContext); 361 } 362 363 367 FlowSessionImpl getActiveSessionInternal() throws IllegalStateException { 368 assertActive(); 369 return (FlowSessionImpl)flowSessions.getLast(); 370 } 371 372 376 protected void setCurrentState(State newState) { 377 getActiveSessionInternal().setState(newState); 378 } 379 380 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 412 FlowSessionImpl createFlowSession(Flow flow, FlowSessionImpl parent) { 413 return new FlowSessionImpl(flow, parent); 414 } 415 416 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 442 private void assertActive() throws IllegalStateException { 443 if (!isActive()) { 444 throw new IllegalStateException ( 445 "This flow execution is not active, it has either ended or has never been started."); 446 } 447 } 448 449 452 private Flow getActiveFlow() { 453 return (Flow)getActiveSessionInternal().getDefinition(); 454 } 455 456 459 private State getCurrentState() { 460 return (State)getActiveSessionInternal().getState(); 461 } 462 463 466 public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException { 467 flowId = (String )in.readObject(); 468 flowSessions = (LinkedList )in.readObject(); 469 } 470 471 public void writeExternal(ObjectOutput out) throws IOException { 472 out.writeObject(flowId); 473 out.writeObject(flowSessions); 474 } 475 476 public String 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 494 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 506 void setListeners(FlowExecutionListeners listeners) { 507 Assert.notNull(listeners, "The execution listener list is required"); 508 this.listeners = listeners; 509 } 510 511 514 void setAttributes(AttributeMap attributes) { 515 Assert.notNull(conversationScope, "The execution attribute map is required"); 516 this.attributes = attributes; 517 } 518 519 522 void setConversationScope(MutableAttributeMap conversationScope) { 523 Assert.notNull(conversationScope, "The conversation scope map is required"); 524 this.conversationScope = conversationScope; 525 } 526 527 530 String getFlowId() { 531 return flowId; 532 } 533 534 537 LinkedList getFlowSessions() { 538 return flowSessions; 539 } 540 541 544 boolean hasSessions() { 545 return !flowSessions.isEmpty(); 546 } 547 548 551 boolean hasSubflowSessions() { 552 return flowSessions.size() > 1; 553 } 554 555 558 FlowSessionImpl getRootSession() { 559 return (FlowSessionImpl)flowSessions.getFirst(); 560 } 561 562 566 ListIterator getSubflowSessionIterator() { 567 return flowSessions.listIterator(1); 568 } 569 570 } | Popular Tags |