001: /**
002: * Copyright 2006 Webmedia Group Ltd.
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: **/package org.araneaframework.framework.container;
016:
017: import java.io.Serializable;
018: import java.util.HashMap;
019: import java.util.Iterator;
020: import java.util.LinkedList;
021: import java.util.Map;
022: import org.apache.commons.collections.Closure;
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.araneaframework.Component;
026: import org.araneaframework.Environment;
027: import org.araneaframework.EnvironmentAwareCallback;
028: import org.araneaframework.OutputData;
029: import org.araneaframework.Widget;
030: import org.araneaframework.core.ApplicationWidget;
031: import org.araneaframework.core.Assert;
032: import org.araneaframework.core.BaseApplicationWidget;
033: import org.araneaframework.core.BaseWidget;
034: import org.araneaframework.core.StandardEnvironment;
035: import org.araneaframework.core.util.ComponentUtil;
036: import org.araneaframework.core.util.ExceptionUtil;
037: import org.araneaframework.framework.EmptyCallStackException;
038: import org.araneaframework.framework.FlowContext;
039: import org.araneaframework.framework.FlowContextWidget;
040: import org.araneaframework.http.WindowScrollPositionContext;
041:
042: /**
043: * A {@link org.araneaframework.framework.FlowContext} where the flows are structured as a stack.
044: *
045: * @author "Toomas Römer" <toomas@webmedia.ee>
046: * @author Jevgeni Kabanov (ekabanov <i>at</i> araneaframework <i>dot</i> org)
047: */
048: public class StandardFlowContainerWidget extends BaseApplicationWidget
049: implements FlowContextWidget {
050: //*******************************************************************
051: // CONSTANTS
052: //*******************************************************************
053: private static final Log log = LogFactory
054: .getLog(StandardFlowContainerWidget.class);
055:
056: private static final String BASE_FLOW_KEY = "f";
057: private static final String TOP_FLOW_KEY = BASE_FLOW_KEY + 0;
058:
059: //*******************************************************************
060: // FIELDS
061: //*******************************************************************
062: /**
063: * The stack of all the calls.
064: */
065: protected LinkedList callStack = new LinkedList();
066: /**
067: * The top callable widget.
068: */
069: protected Widget top;
070: protected boolean finishable = true;
071:
072: private Map nestedEnvironmentEntries = new HashMap();
073: private Map nestedEnvEntryStacks = new HashMap();
074:
075: //*******************************************************************
076: // CONSTRUCTORS
077: //*******************************************************************
078:
079: /**
080: * Constructs a {@link StandardFlowContainerWidget} with <code>topWidget</code>
081: * being the first flow on the top of flow stack.
082: */
083: public StandardFlowContainerWidget(Widget topWidget) {
084: this .top = topWidget;
085: }
086:
087: public StandardFlowContainerWidget() {
088: }
089:
090: //*******************************************************************
091: // PUBLIC METHODS
092: //*******************************************************************
093:
094: public void setTop(Widget topWidget) {
095: this .top = topWidget;
096: }
097:
098: /**
099: * Determines whether this {@link StandardFlowContainerWidget} will ever
100: * return control to {@link FlowContext} higher in the {@link org.araneaframework.Component}
101: * hierarchy. If such {@link FlowContext} exists and finishable is set to true, this
102: * {@link StandardFlowContainerWidget} will return control to it when last running flow
103: * inside it is finished ({@link FlowContext#finish(Object)}) or canceled ({@link FlowContext#cancel()}).
104: *
105: * Default is <code>true</code>.
106: * @param finishable
107: * @since 1.1
108: */
109: public void setFinishable(boolean finishable) {
110: this .finishable = finishable;
111: }
112:
113: public void start(Widget flow) {
114: start(flow, null, null);
115: }
116:
117: public void start(Widget flow, Handler handler) {
118: start(flow, null, handler);
119: }
120:
121: public void start(Widget flow, Configurator configurator,
122: Handler handler) {
123: TransitionHandler transitionHandler = getTransitionHandler();
124: StartClosure startClosure = new StartClosure(flow,
125: configurator, handler);
126: doTransition(transitionHandler, FlowContext.TRANSITION_START,
127: startClosure);
128: }
129:
130: public void replace(Widget flow) {
131: replace(flow, null);
132: }
133:
134: public void replace(Widget flow, Configurator configurator) {
135: TransitionHandler transitionHandler = getTransitionHandler();
136: ReplaceClosure replaceClosure = new ReplaceClosure(flow,
137: configurator);
138: doTransition(transitionHandler, FlowContext.TRANSITION_REPLACE,
139: replaceClosure);
140: }
141:
142: public void finish(Object returnValue) {
143: TransitionHandler transitionHandler = getTransitionHandler();
144: FinishClosure finishClosure = new FinishClosure(returnValue);
145: doTransition(transitionHandler, FlowContext.TRANSITION_FINISH,
146: finishClosure);
147: }
148:
149: public void cancel() {
150: TransitionHandler transitionHandler = getTransitionHandler();
151: CancelClosure cancelClosure = new CancelClosure();
152: doTransition(transitionHandler, FlowContext.TRANSITION_CANCEL,
153: cancelClosure);
154: }
155:
156: public void reset(final EnvironmentAwareCallback callback) {
157: TransitionHandler transitionHandler = getTransitionHandler();
158: ResetClosure resetClosure = new ResetClosure(callback);
159: doTransition(transitionHandler, FlowContext.TRANSITION_RESET,
160: resetClosure);
161: }
162:
163: public TransitionHandler getTransitionHandler() {
164: CallFrame activeCallFrame = getActiveCallFrame();
165: if (activeCallFrame != null) {
166: TransitionHandler transitionHandler = activeCallFrame
167: .getTransitionHandler();
168: return transitionHandler != null ? transitionHandler
169: : new StandardTransitionHandler();
170: }
171: return new StandardTransitionHandler();
172: }
173:
174: public void setTransitionHandler(TransitionHandler transitionHandler) {
175: CallFrame activeCallFrame = getActiveCallFrame();
176: if (activeCallFrame != null)
177: activeCallFrame.setTransitionHandler(transitionHandler);
178: }
179:
180: public FlowContext.FlowReference getCurrentReference() {
181: return new FlowReference();
182: }
183:
184: public void addNestedEnvironmentEntry(ApplicationWidget scope,
185: final Object entryId, Object envEntry) {
186: Assert.notNullParam(scope, "scope");
187: Assert.notNullParam(entryId, "entryId");
188:
189: pushGlobalEnvEntry(entryId, envEntry);
190:
191: BaseWidget scopedWidget = new BaseWidget() {
192: protected void destroy() throws Exception {
193: popGlobalEnvEntry(entryId);
194: }
195: };
196: ComponentUtil.addListenerComponent(scope, scopedWidget);
197: }
198:
199: public boolean isNested() {
200: return callStack.size() != 0;
201: }
202:
203: //*******************************************************************
204: // PROTECTED LIFECYCLE METHODS
205: //*******************************************************************
206:
207: protected void init() throws Exception {
208: super .init();
209:
210: refreshGlobalEnvironment();
211:
212: if (top != null) {
213: start(top, null, null);
214: top = null;
215: }
216: }
217:
218: protected void destroy() throws Exception {
219: if (callStack.size() > 0)
220: callStack.removeFirst();
221:
222: for (Iterator i = callStack.iterator(); i.hasNext();) {
223: CallFrame frame = (CallFrame) i.next();
224: i.remove();
225:
226: frame.getWidget()._getComponent().destroy();
227: }
228:
229: super .destroy();
230: }
231:
232: /**
233: * Invokes render on the top frame on the stack of callframes.
234: */
235: protected void render(OutputData output) throws Exception {
236: //Don't render empty callstack
237: if (getCallStack().size() == 0)
238: return;
239:
240: CallFrame frame = (CallFrame) callStack.getFirst();
241:
242: getWidget(frame.getName())._getWidget().render(output);
243: }
244:
245: //*******************************************************************
246: // IMPL SPECIFIC PROTECTED METHODS
247: //*******************************************************************
248: protected void putLocalEnvironmentEntries(
249: Map nestedEnvironmentEntries) {
250: nestedEnvironmentEntries.put(FlowContext.class, this );
251: }
252:
253: protected Environment getChildWidgetEnvironment() throws Exception {
254: return new StandardEnvironment(getEnvironment(),
255: nestedEnvironmentEntries);
256: }
257:
258: /**
259: * Returns a new CallFrame constructed of the callable, configurator and handler.
260: */
261: protected CallFrame makeCallFrame(Widget callable,
262: Configurator configurator, Handler handler,
263: CallFrame previous) {
264: return new CallFrame(callable, configurator, handler, previous);
265: }
266:
267: protected CallFrame getActiveCallFrame() {
268: return callStack.size() == 0 ? null : (CallFrame) callStack
269: .getFirst();
270: }
271:
272: /** @since 1.1 */
273: protected Widget getActiveFlow() {
274: CallFrame frame = getActiveCallFrame();
275: return frame != null ? frame.getWidget() : null;
276: }
277:
278: /**
279: * Activates the widget represented by the {@link CallFrame}.
280: * @since 1.1 */
281: protected void addFrameWidget(CallFrame frame) {
282: final Widget flow = frame.getWidget();
283: addWidget(frame.getName(), flow);
284: }
285:
286: /** @since 1.1 */
287: protected void doTransition(TransitionHandler transitionHandler,
288: int transitionType, Closure closure) {
289: transitionHandler.doTransition(transitionType, getActiveFlow(),
290: closure);
291: }
292:
293: /** @since 1.1 */
294: protected void doReset(final EnvironmentAwareCallback callback) {
295: if (log.isDebugEnabled())
296: log.debug("Resetting flows '" + callStack + "'");
297:
298: for (Iterator i = callStack.iterator(); i.hasNext();) {
299: CallFrame frame = (CallFrame) i.next();
300:
301: _getChildren().put(frame.getName(), frame.getWidget());
302: removeWidget(frame.getName());
303: }
304:
305: callStack.clear();
306:
307: if (callback != null)
308: try {
309: callback.call(getChildWidgetEnvironment());
310: } catch (Exception e) {
311: throw ExceptionUtil.uncheckException(e);
312: }
313: }
314:
315: /** @since 1.1 */
316: protected void doStart(Widget flow, Configurator configurator,
317: Handler handler) {
318: Assert.notNullParam(flow, "flow");
319:
320: CallFrame previous = getActiveCallFrame();
321: CallFrame frame = makeCallFrame(flow, configurator, handler,
322: previous);
323:
324: if (log.isDebugEnabled())
325: log.debug("Starting flow '" + flow.getClass().getName()
326: + "'");
327:
328: if (previous != null
329: && _getChildren().get(previous.getName()) != null) {
330: ((Widget) getChildren().get(previous.getName()))
331: ._getComponent().disable();
332: _getChildren().remove(previous.getName());
333: }
334:
335: callStack.addFirst(frame);
336:
337: addFrameWidget(frame);
338:
339: if (configurator != null) {
340: try {
341: configurator.configure(flow);
342: } catch (Exception e) {
343: throw ExceptionUtil.uncheckException(e);
344: }
345: }
346: }
347:
348: /** @since 1.1 */
349: protected void doFinish(Object returnValue) {
350: if (callStack.size() == 0)
351: throw new EmptyCallStackException();
352:
353: CallFrame previousFrame = (CallFrame) callStack.removeFirst();
354: CallFrame frame = getActiveCallFrame();
355:
356: if (log.isDebugEnabled())
357: log.debug("Finishing flow '"
358: + previousFrame.getWidget().getClass().getName()
359: + "'");
360:
361: removeWidget(previousFrame.getName());
362: if (frame != null) {
363: _getChildren().put(frame.getName(), frame.getWidget());
364: ((Component) getChildren().get(frame.getName()))
365: ._getComponent().enable();
366: }
367:
368: if (previousFrame.getHandler() != null) {
369: try {
370: previousFrame.getHandler().onFinish(returnValue);
371: } catch (Exception e) {
372: throw ExceptionUtil.uncheckException(e);
373: }
374: }
375:
376: if (finishable && callStack.size() == 0) {
377: FlowContext parentFlowContainer = (FlowContext) getEnvironment()
378: .getEntry(FlowContext.class);
379: if (parentFlowContainer != null) {
380: parentFlowContainer.finish(returnValue);
381: }
382: }
383: }
384:
385: /** @since 1.1 */
386: protected void doCancel() {
387: if (callStack.size() == 0)
388: throw new EmptyCallStackException();
389:
390: CallFrame previousFrame = (CallFrame) callStack.removeFirst();
391: CallFrame frame = getActiveCallFrame();
392:
393: if (log.isDebugEnabled())
394: log.debug("Cancelling flow '"
395: + previousFrame.getWidget().getClass().getName()
396: + "'");
397:
398: removeWidget(previousFrame.getName());
399: if (frame != null) {
400: _getChildren().put(frame.getName(), frame.getWidget());
401: ((Component) getChildren().get(frame.getName()))
402: ._getComponent().enable();
403: }
404:
405: if (previousFrame.getHandler() != null)
406: try {
407: previousFrame.getHandler().onCancel();
408: } catch (Exception e) {
409: throw ExceptionUtil.uncheckException(e);
410: }
411:
412: if (finishable && callStack.size() == 0) {
413: FlowContext parentFlowContainer = (FlowContext) getEnvironment()
414: .getEntry(FlowContext.class);
415: if (parentFlowContainer != null) {
416: parentFlowContainer.cancel();
417: }
418: }
419: }
420:
421: /** @since 1.1 */
422: protected void doReplace(Widget flow, Configurator configurator) {
423: Assert.notNullParam(flow, "flow");
424:
425: CallFrame previousFrame = (CallFrame) callStack.removeFirst();
426: CallFrame frame = makeCallFrame(flow, configurator,
427: previousFrame.getHandler(), previousFrame);
428:
429: if (log.isDebugEnabled())
430: log
431: .debug("Replacing flow '"
432: + previousFrame.getWidget().getClass()
433: .getName() + "' with flow '"
434: + flow.getClass().getName() + "'");
435:
436: removeWidget(previousFrame.getName());
437:
438: callStack.addFirst(frame);
439:
440: addFrameWidget(frame);
441:
442: if (configurator != null) {
443: try {
444: configurator.configure(flow);
445: } catch (Exception e) {
446: throw ExceptionUtil.uncheckException(e);
447: }
448: }
449: }
450:
451: protected LinkedList getCallStack() {
452: return callStack;
453: }
454:
455: //*******************************************************************
456: // PRIVATE METHODS
457: //*******************************************************************
458: private void refreshGlobalEnvironment() {
459: nestedEnvironmentEntries.clear();
460:
461: putLocalEnvironmentEntries(nestedEnvironmentEntries);
462:
463: for (Iterator i = nestedEnvEntryStacks.entrySet().iterator(); i
464: .hasNext();) {
465: Map.Entry entry = (Map.Entry) i.next();
466: Object entryId = entry.getKey();
467: LinkedList stack = (LinkedList) entry.getValue();
468: if (stack.size() > 0) {
469: Object envEntry = stack.getFirst();
470: nestedEnvironmentEntries.put(entryId, envEntry);
471: }
472: }
473: }
474:
475: private LinkedList getEnvEntryStack(Object entryId) {
476: LinkedList envEntryStack = (LinkedList) nestedEnvEntryStacks
477: .get(entryId);
478:
479: if (envEntryStack == null) {
480: envEntryStack = new LinkedList();
481: nestedEnvEntryStacks.put(entryId, envEntryStack);
482: }
483:
484: return envEntryStack;
485: }
486:
487: private void pushGlobalEnvEntry(Object entryId, Object envEntry) {
488: getEnvEntryStack(entryId).addFirst(envEntry);
489:
490: refreshGlobalEnvironment();
491: }
492:
493: private void popGlobalEnvEntry(Object entryId) {
494: getEnvEntryStack(entryId).removeFirst();
495:
496: refreshGlobalEnvironment();
497: }
498:
499: //*******************************************************************
500: // PROTECTED CLASSES
501: //*******************************************************************
502:
503: protected class FlowReference implements FlowContext.FlowReference {
504: private int currentDepth = StandardFlowContainerWidget.this .callStack
505: .size();
506:
507: public void reset(EnvironmentAwareCallback callback)
508: throws Exception {
509: Iterator i = callStack.iterator();
510: while (i.hasNext() && callStack.size() > currentDepth) {
511: CallFrame frame = (CallFrame) i.next();
512:
513: _getChildren().put(frame.getName(), frame.getWidget());
514: removeWidget(frame.getName());
515:
516: i.remove();
517: }
518:
519: if (callStack.size() > 0) {
520: CallFrame frame = (CallFrame) callStack.getFirst();
521: _getChildren().put(frame.getName(), frame.getWidget());
522: ((Component) getChildren().get(frame.getName()))
523: ._getComponent().enable();
524: }
525:
526: callback.call(getChildWidgetEnvironment());
527: }
528: }
529:
530: /**
531: * A widget, configurator and a handler are encapsulated into one logical structure,
532: * a call frame. Class is used internally.
533: */
534: protected static class CallFrame implements Serializable {
535: private Widget widget;
536: private Configurator configurator;
537: private Handler handler;
538: private String name;
539: private TransitionHandler transitionHandler;
540:
541: protected CallFrame(Widget widget, Configurator configurator,
542: Handler handler, CallFrame previous) {
543: this .configurator = configurator;
544: this .handler = handler;
545: this .widget = widget;
546:
547: if (previous == null)
548: name = TOP_FLOW_KEY;
549: else {
550: name = BASE_FLOW_KEY
551: + (Integer.parseInt(previous.getName()
552: .substring(BASE_FLOW_KEY.length())) + 1);
553: }
554: }
555:
556: public Configurator getConfigurator() {
557: return configurator;
558: }
559:
560: public Handler getHandler() {
561: return handler;
562: }
563:
564: public Widget getWidget() {
565: return widget;
566: }
567:
568: /**
569: * @return unique child key for contained {@link Widget}.
570: * @since 1.1
571: */
572: public String getName() {
573: return name;
574: }
575:
576: public String toString() {
577: return widget.getClass().getName();
578: }
579:
580: public TransitionHandler getTransitionHandler() {
581: return this .transitionHandler;
582: }
583:
584: protected void setTransitionHandler(
585: TransitionHandler transitionHandler) {
586: this .transitionHandler = transitionHandler;
587: }
588: }
589:
590: /* Protected Closure classes for executing flow navigation events from callbacks. */
591: /** @since 1.1 */
592: protected class CancelClosure implements Closure, Serializable {
593: private static final long serialVersionUID = 1L;
594:
595: public void execute(Object obj) {
596: doCancel();
597: }
598: }
599:
600: /** @since 1.1 */
601: protected class ResetClosure implements Closure, Serializable {
602: private static final long serialVersionUID = 1L;
603: protected EnvironmentAwareCallback callback;
604:
605: public ResetClosure(EnvironmentAwareCallback callback) {
606: this .callback = callback;
607: }
608:
609: public void execute(Object obj) {
610: doReset(callback);
611: }
612: }
613:
614: /** @since 1.1 */
615: protected class FinishClosure implements Closure, Serializable {
616: private static final long serialVersionUID = 1L;
617: protected Object result;
618:
619: public FinishClosure(Object result) {
620: this .result = result;
621: }
622:
623: public void execute(Object obj) {
624: doFinish(result);
625: }
626: }
627:
628: /** @since 1.1 */
629: protected class ReplaceClosure implements Closure, Serializable {
630: private static final long serialVersionUID = 1L;
631: protected Widget flow;
632: protected Configurator configurator;
633:
634: public ReplaceClosure(Widget flow, Configurator configurator) {
635: this .flow = flow;
636: this .configurator = configurator;
637: }
638:
639: public void execute(Object obj) {
640: doReplace(flow, configurator);
641: }
642: }
643:
644: /** @since 1.1 */
645: protected class StartClosure implements Closure, Serializable {
646: private static final long serialVersionUID = 1L;
647: protected Widget flow;
648: protected Configurator configurator;
649: protected Handler handler;
650:
651: public StartClosure(Widget flow, Configurator configurator,
652: Handler handler) {
653: this .flow = flow;
654: this .configurator = configurator;
655: this .handler = handler;
656: }
657:
658: public void execute(Object obj) {
659: doStart(flow, configurator, handler);
660: }
661: }
662:
663: public static class StandardTransitionHandler implements
664: FlowContext.TransitionHandler {
665: private static final long serialVersionUID = 1L;
666:
667: public void doTransition(int transitionType, Widget activeFlow,
668: Closure transition) {
669: notifyScrollContext(transitionType, activeFlow);
670: transition.execute(activeFlow);
671: }
672:
673: protected void notifyScrollContext(int transitionType,
674: Widget activeFlow) {
675: if (activeFlow == null)
676: return;
677: WindowScrollPositionContext scrollCtx = (WindowScrollPositionContext) activeFlow
678: .getEnvironment().getEntry(
679: WindowScrollPositionContext.class);
680: if (scrollCtx != null) {
681: switch (transitionType) {
682: case FlowContext.TRANSITION_START:
683: scrollCtx.push();
684: break;
685:
686: case FlowContext.TRANSITION_FINISH:
687: case FlowContext.TRANSITION_CANCEL:
688: scrollCtx.pop();
689: break;
690:
691: case FlowContext.TRANSITION_REPLACE:
692: scrollCtx.resetCurrent();
693: break;
694:
695: case FlowContext.TRANSITION_RESET:
696: scrollCtx.reset();
697: break;
698: }
699: }
700: }
701: }
702: }
|