1 /*
2 * Copyright 1999-2001,2004 The Apache Software Foundation.
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
17 package org.apache.commons.workflow.base;
18
19
20 import java.util.EmptyStackException;
21 import org.apache.commons.collections.ArrayStack;
22 import org.apache.commons.jxpath.JXPathContext;
23 import org.apache.commons.workflow.Activity;
24 import org.apache.commons.workflow.Block;
25 import org.apache.commons.workflow.BlockState;
26 import org.apache.commons.workflow.Context;
27 import org.apache.commons.workflow.ContextEvent;
28 import org.apache.commons.workflow.ContextListener;
29 import org.apache.commons.workflow.Owner;
30 import org.apache.commons.workflow.Step;
31 import org.apache.commons.workflow.StepException;
32 import org.apache.commons.workflow.Scope;
33 import org.apache.commons.workflow.util.ContextSupport;
34
35
36 /**
37 * <strong>BaseContext</strong> is a basic <code>Context</code> implementation
38 * that can serve as a convenient base class for more sophisticated
39 * <code>Context</code> implementations.
40 *
41 * <p><strong>WARNING</strong> - No synchronization is performed within this
42 * class. If it is used in a multiple thread environment, callers must
43 * take suitable precations.</p>
44 *
45 * @version $Revision: 155475 $ $Date: 2005-02-26 13:31:11 +0000 (Sat, 26 Feb 2005) $
46 * @author Craig R. McClanahan
47 */
48
49 public class BaseContext implements Context {
50
51
52
53 // ----------------------------------------------------------- Constructors
54
55
56 /**
57 * Construct a new BaseContext with all default values.
58 */
59 public BaseContext() {
60
61 super();
62 names[LOCAL_SCOPE] = "local";
63 scopes[LOCAL_SCOPE] = new BaseScope();
64
65 }
66
67
68 // ----------------------------------------------------- Instance Variables
69
70
71 /**
72 * The <code>Activity</code> that we are associated with and executing
73 * <code>Steps</code> from (if any).
74 */
75 protected Activity activity = null;
76
77
78 /**
79 * The bean representing the information about this Context made visible
80 * through JXPath.
81 */
82 protected BaseContextBean bean = null;
83
84
85 /**
86 * The suspended "next step" Step for each in-progress Activity that has
87 * issued a <code>call()</code> to execute a subordinate Activity.
88 */
89 protected ArrayStack calls = new ArrayStack();
90
91
92 /**
93 * The set of names associated with the registered <code>Scopes</code>.
94 */
95 protected String names[] = new String[MAX_SCOPES];
96
97
98 /**
99 * The <code>Step</code> that will be executed first the next time that
100 * <code>execute()</code> is called. This is maintained dynamically as
101 * execution proceeds, much like the "next instruction pointer" of a
102 * CPU tracks what instruction will be executed next.
103 */
104 protected Step nextStep = null;
105
106
107 /**
108 * The set of <code>Scopes</code> that have been associated with
109 * this Context. When initially created, every Context has a
110 * Scope attached to identifier LOCAL_SCOPE already created.
111 */
112 protected Scope scopes[] = new Scope[MAX_SCOPES];
113
114
115 /**
116 * The evaluation stack of nameless objects being processed.
117 */
118 protected ArrayStack stack = new ArrayStack();
119
120
121 /**
122 * The BlockState stack of BlockState objects used to manage iteration.
123 */
124 protected ArrayStack state = new ArrayStack();
125
126
127 /**
128 * The event listener support object for this <code>Context</code>.
129 */
130 protected ContextSupport support = new ContextSupport(this);
131
132
133 /**
134 * The suspension flag. If this is set when a particular <code>Step</code>
135 * returns, control will be returned from our <code>execute()</code>
136 * method to allow interaction with the rest of the application. The
137 * next time that <code>execute()</code> is called, execution will
138 * resume with the next scheduled step.
139 */
140 protected boolean suspend = false;
141
142
143 // --------------------------------------------------- Scope Access Methods
144
145
146 // These methods provide Steps with the ability to get and set arbitrary
147 // objects in arbitrary scopes. When a scope is not mentioned explicitly,
148 // either LOCAL_SCOPE will be used, or scopes will be searched in
149 // increasing order of their identifiers, as specified in the various
150 // method descriptions.
151
152
153 /**
154 * Return true if a bean with the specified key exist in any specified
155 * scope. Scopes will be searched in ascending order of their identifiers,
156 * starting with LOCAL_SCOPE.
157 *
158 * @param key Key of the bean to be searched for (cannot be null)
159 */
160 public boolean contains(String key) {
161
162 for (int i = 0; i < MAX_SCOPES; i++) {
163 if (scopes[i] == null)
164 continue;
165 if (scopes[i].containsKey(key))
166 return (true);
167 }
168 return (false);
169
170 }
171
172
173 /**
174 * Does a bean with the specified key exist in the specified scope?
175 *
176 * @param key Key of the bean to be searched for (cannot be null)
177 * @param scope Identifier of the scope to be returned.
178 */
179 public boolean contains(String key, int scope) {
180
181 if (scopes[scope] == null)
182 return (false);
183 else
184 return (scopes[scope].containsKey(key));
185
186 }
187
188
189 /**
190 * Return the bean associated with the specified key, if it exists in
191 * any scope. Scopes will be searched in increasing order of their
192 * identifiers, starting with LOCAL_SCOPE. If the underlying Scope
193 * allows null values, a <code>null</code> return will be ambiguous.
194 * Therefore, you can use the <code>contains()</code> method to determine
195 * whether the key actually exists.
196 *
197 * @param key Key of the bean to be retrieved (cannot be null)
198 */
199 public Object get(String key) {
200
201 for (int i = 0; i < MAX_SCOPES; i++) {
202 if (scopes[i] == null)
203 continue;
204 Object bean = scopes[i].get(key);
205 if (bean != null)
206 return (bean);
207 }
208 return (null);
209
210 }
211
212
213 /**
214 * Return the bean associated with the specified key, if it exists in
215 * the specified scope. If the underlying Scope allows null values,
216 * a <code>null</code> return will be ambiguous. Therefore, you can
217 * use the <code>contains()</code> method to determine whether the
218 * key actually exists.
219 *
220 * @param key Key of the bean to be searched for
221 * @param scope Identifier of the scope to be searched
222 */
223 public Object get(String key, int scope) {
224
225 if (scopes[scope] == null)
226 return (null);
227 else
228 return (scopes[scope].get(key));
229
230 }
231
232
233 /**
234 * Store the specified bean under the specified key, in local scope,
235 * replacing any previous value for that key. Any previous value will
236 * be returned.
237 *
238 * @param key Key of the bean to be stored (cannot be null)
239 * @param value Value of the bean to be stored
240 */
241 public Object put(String key, Object value) {
242
243 return (scopes[LOCAL_SCOPE].put(key, value));
244
245 }
246
247
248 /**
249 * Store the specified bean under the specified key, in the specified
250 * scope, replacing any previous value for that key. Any previous value
251 * will be returned.
252 *
253 * @param key Key of the bean to be stored (cannot be null)
254 * @param value Value of the bean to be stored
255 * @param scope Identifier of the scope to use for storage
256 */
257 public Object put(String key, Object value, int scope) {
258
259 if (scopes[scope] == null)
260 return (null);
261 else
262 return (scopes[scope].put(key, value));
263
264 }
265
266
267 /**
268 * Remove any existing value for the specified key, in any scope.
269 * Scopes will be searched in ascending order of their identifiers,
270 * starting with LOCAL_SCOPE. Any previous value for this key will
271 * be returned.
272 *
273 * @param key Key of the bean to be removed
274 */
275 public Object remove(String key) {
276
277 return (scopes[LOCAL_SCOPE].remove(key));
278
279 }
280
281
282 /**
283 * Remove any existing value for the specified key, from the specified
284 * scope. Any previous value for this key will be returned.
285 *
286 * @param key Key of the bean to be removed
287 * @param scope Scope the bean to be removed from
288 */
289 public Object remove(String key, int scope) {
290
291 if (scopes[scope] == null)
292 return (null);
293 else
294 return (scopes[scope].remove(key));
295
296 }
297
298
299
300 // --------------------------------------------- Scope Registration Methods
301
302
303 // These methods provide capabilities to register and retrieve
304 // Scope implementations. Workflow engine implementations will
305 // register desired scopes when they create Context instances.
306
307
308 /**
309 * <p>Register a Scope implementation under the specified identifier.
310 * It is not legal to replace the LOCAL_SCOPE implementation that is
311 * provided by the Context implementation.</p>
312 *
313 * <p>In addition to registering the new Scope such that it can be
314 * accessed dirctly via calls like <code>Context.get(String,int)</code>,
315 * the Scope <code>impl</code> object will also be added to the LOCAL
316 * Scope under the same name. This makes possible a single unified
317 * namespace of all accessible objects that can be navigated by
318 * expression languages.</p>
319 *
320 * @param scope Scope identifier to register under
321 * @param name Scope name to register under
322 * @param impl Scope implementation to be registered (or null to
323 * remove a previous registration)
324 * @exception IllegalArgumentException if you attempt to register
325 * or deregister the local scope
326 */
327 public void addScope(int scope, String name, Scope impl) {
328
329 if (scope == LOCAL_SCOPE)
330 throw new IllegalArgumentException("Cannot replace local scope");
331 if (impl == null) {
332 getScope(LOCAL_SCOPE).remove(name);
333 names[scope] = null;
334 scopes[scope] = null;
335 } else {
336 getScope(LOCAL_SCOPE).put(name, impl);
337 names[scope] = name;
338 scopes[scope] = impl;
339 }
340 bean = null;
341
342 }
343
344
345 /**
346 * Return the Scope instance for our local Scope as a simple property.
347 */
348 public Scope getLocal() {
349
350 return (getScope(LOCAL_SCOPE));
351
352 }
353
354
355 /**
356 * Return the Scope implementation registered for the specified
357 * identifier, if any; otherwise, return <code>null</code>.
358 *
359 * @param scope Scope identifier to select
360 */
361 public Scope getScope(int scope) {
362
363 if ((scope >= 0) && (scope < MAX_SCOPES))
364 return (scopes[scope]);
365 else
366 return (null);
367
368 }
369
370
371 /**
372 * Return the Scope implementation registered for the specified
373 * name, if any; otherwise, return <code>null</code>.
374 *
375 * @param name Scope name to select
376 */
377 public Scope getScope(String name) {
378
379 return (getScope(getScopeId(name)));
380
381 }
382
383
384 /**
385 * Return the Scope identifier registered for the specified
386 * name, if any; otherwise, return <code>-1</code>.
387 *
388 * @param name Scope name to select
389 */
390 public int getScopeId(String name) {
391
392 for (int i = 0; i < names.length; i++) {
393 if (names[i] == null)
394 continue;
395 if (names[i].equals(name))
396 return (i);
397 }
398 return (-1);
399
400 }
401
402
403 // ----------------------------------------------- Evaluation Stack Methods
404
405
406 /**
407 * Clear the evaluation stack.
408 */
409 public void clear() {
410
411 stack.clear();
412
413 }
414
415
416 /**
417 * Is the evaluation stack currently empty?
418 */
419 public boolean isEmpty() {
420
421 return (stack.size() == 0);
422
423 }
424
425
426 /**
427 * Return the top item from the evaluation stack without removing it.
428 *
429 * @exception EmptyStackException if the stack is empty
430 */
431 public Object peek() throws EmptyStackException {
432
433 return (stack.peek());
434
435 }
436
437
438 /**
439 * Pop and return the top item from the evaluation stack.
440 *
441 * @exception EmptyStackException if the stack is empty
442 */
443 public Object pop() throws EmptyStackException {
444
445 return (stack.pop());
446
447 }
448
449
450 /**
451 * Push a new item onto the top of the evaluation stack.
452 *
453 * @param item New item to be pushed
454 */
455 public void push(Object item) {
456
457 stack.push(item);
458
459 }
460
461
462 // ----------------------------------------------- BlockState Stack Methods
463
464
465 /**
466 * Clear the BlockState stack.
467 */
468 public void clearBlockState() {
469
470 state.clear();
471
472 }
473
474
475 /**
476 * Is the BlockState stack currently empty?
477 */
478 public boolean isEmptyBlockState() {
479
480 return (state.size() == 0);
481
482 }
483
484
485 /**
486 * Return the top item from the BlockState stack without removing it.
487 *
488 * @exception EmptyStackException if the stack is empty
489 */
490 public BlockState peekBlockState() throws EmptyStackException {
491
492 return ((BlockState) state.peek());
493
494 }
495
496
497 /**
498 * Pop and return the top item from the BlockState stack.
499 *
500 * @exception EmptyStackException if the stack is empty
501 */
502 public BlockState popBlockState() throws EmptyStackException {
503
504 return ((BlockState) state.pop());
505
506 }
507
508
509 /**
510 * Push a new item onto the top of the BlockState stack.
511 *
512 * @param item New item to be pushed
513 */
514 public void pushBlockState(BlockState item) {
515
516 state.push(item);
517
518 }
519
520
521 // ---------------------------------------------- Dynamic Execution Methods
522
523
524 // These methods are used to request the dynamic execution of the
525 // Steps related to a particular Activity.
526
527
528 /**
529 * <p>Save the execution state (ie the currently assigned next step)
530 * of the Activity we are currently executing, and begin executing the
531 * specified Activity. When that Activity exits (either normally
532 * or by throwing an exception), the previous Activity will be resumed
533 * where it left off.
534 *
535 * @param activity The Activity to be called
536 */
537 public void call(Activity activity) {
538
539 // Save the next Step for the current Activity (if we have
540 // any remaining steps to worry about -- a call on the last
541 // step of an activity is more like a non-local goto)
542 if (this.nextStep != null)
543 calls.push(this.nextStep);
544
545 // Forward control to the first Step of the new Activity
546 this.activity = activity;
547 this.nextStep = activity.getFirstStep();
548
549 }
550
551
552 /**
553 * <p>Execute the <code>Step</code> currently identified as the next
554 * step, and continue execution until there is no next step, or until
555 * the <code>suspend</code> property has been set to true. Upon
556 * completion of an activity, any execution state that was saved to due
557 * to utilizing the <code>call()</code> method will be restored, and
558 * the saved Activity execution shall be resumed.
559 *
560 * @exception StepException if an exception is thrown by the
561 * <code>execute()</code> method of a Step we have executed
562 * @exception IllegalStateException if there is no defined next step
563 * (either because there is no Activity, or because we have already
564 * completed all the steps of this activity)
565 */
566 public void execute() throws StepException {
567
568 // Do we actually have a next step to be performed
569 if (activity == null)
570 throw new IllegalStateException("No Activity has been selected");
571 if (nextStep == null)
572 nextStep = activity.getFirstStep();
573 if (nextStep == null)
574 throw new IllegalStateException("Activity has been completed");
575
576 // Reset the suspend flag until set by another step
577 suspend = false;
578
579 // Send a beforeActivity() event to interested listeners
580 support.fireBeforeActivity(nextStep);
581
582 // Perform execution until suspended or completed
583 Step thisStep = null;
584 StepException exception = null;
585 while (true) {
586
587 // Process a suspension of Activity execution
588 if (suspend)
589 break; // Suspend set by a Step
590
591 // Process completion of an Activity
592 if (nextStep == null) {
593
594 // If there are no active calls, we are done
595 if (calls.empty())
596 break;
597
598 // If there are active calls, resume the most recent one
599 try {
600 nextStep = (Step) calls.pop();
601 Owner owner = nextStep.getOwner();
602 while (!(owner instanceof Activity)) {
603 owner = ((Step) owner).getOwner();
604 }
605 this.activity = (Activity) owner;
606 } catch (EmptyStackException e) {
607 ; // Can not happen
608 }
609 continue;
610
611 }
612
613 // Execute the (now) current Step
614 thisStep = nextStep;
615 nextStep = thisStep.getNextStep(); // Assume sequential execution
616 try {
617 support.fireBeforeStep(thisStep);
618 thisStep.execute(this);
619 support.fireAfterStep(thisStep);
620 } catch (StepException e) {
621 exception = e;
622 support.fireAfterStep(thisStep, exception);
623 break;
624 } catch (Throwable t) {
625 exception = new StepException(t, thisStep);
626 support.fireAfterStep(thisStep, exception);
627 break;
628 }
629
630 }
631
632 // Send an afterActivity event to interested listeners
633 support.fireAfterActivity(thisStep, exception);
634
635 // Rethrow any StepException that was thrown
636 if (exception != null)
637 throw exception;
638
639 }
640
641
642 /**
643 * <p>Return the <code>Activity</code> we will be executing when the
644 * <code>execute()</code> method is called, if any.</p>
645 */
646 public Activity getActivity() {
647
648 return (this.activity);
649
650 }
651
652
653 /**
654 * Return the set of pending Step executions that are pending because
655 * of calls to subordinate Activities have occurred. If there are
656 * no pending Step executions, a zero-length array is returned.
657 */
658 public Step[] getCalls() {
659
660 Step steps[] = new Step[calls.size()];
661 return ((Step[]) calls.toArray(steps));
662
663 }
664
665
666 /**
667 * Return the JXPathContext object that represents a unified namespace
668 * covering all of our registered <code>Scopes</code>.
669 */
670 public JXPathContext getJXPathContext() {
671
672 if (bean == null)
673 bean = new BaseContextBean(this);
674 return (JXPathContext.newContext(bean));
675
676 }
677
678
679 /**
680 * <p>Return the <code>Step</code> that will be executed the next time
681 * that <code>execute()</code> is called, if any.</p>
682 */
683 public Step getNextStep() {
684
685 return (this.nextStep);
686
687 }
688
689
690 /**
691 * <p>Return the suspend flag.</p>
692 */
693 public boolean getSuspend() {
694
695 return (this.suspend);
696
697 }
698
699
700 /**
701 * <p>Set the <code>Activity</code> to be executed, and make the first
702 * defined <code>Step</code> within this <code>Activity</code> the next
703 * action to be performed by <code>execute()</code>.</p>
704 *
705 * <p>If <code>null</code> is passed, any currently associated Activity
706 * will be released, and the evaluation stack and nested call state
707 * stack will be cleared.</p>
708 *
709 * <p><strong>WARNING</strong> - This will have to become more sophisticated
710 * in order to support calling nested Activities.</p>
711 *
712 * @param activity The new Activity to be executed, or <code>null</code>
713 * to release resources
714 */
715 public void setActivity(Activity activity) {
716
717 if (activity == null) {
718 this.activity = null;
719 this.nextStep = null;
720 clear();
721 clearBlockState();
722 } else {
723 this.activity = activity;
724 this.nextStep = activity.getFirstStep();
725 }
726 calls.clear();
727
728 }
729
730
731 /**
732 * <p>Set the <code>Step</code> that will be executed the next time
733 * that <code>execute()</code> is called. This is called by a
734 * <code>Step</code> that wants to perform branching based on some
735 * calculated results.</p>
736 *
737 * @param nextStep The next Step to be executed
738 *
739 * @exception IllegalArgumentException if the specified Step is not
740 * part of the current Activity
741 */
742 public void setNextStep(Step nextStep) {
743
744 // Make sure the specified next Step is within the current Activity
745 if (nextStep != null) {
746 Owner owner = nextStep.getOwner();
747 while (owner instanceof Block)
748 owner = ((Block) owner).getOwner();
749 if (this.activity != (Activity) owner)
750 throw new IllegalArgumentException
751 ("Step is not part of the current Activity");
752 }
753 this.nextStep = nextStep;
754
755 }
756
757
758 /**
759 * <p>Set the suspend flag. This is called by a <code>Step</code> that
760 * wants to signal the <code>Context</code> to return control to the
761 * caller of the <code>execute()</code> method before executing the
762 * next <code>Step</code> in the current activity.</p>
763 *
764 * @param suspend The new suspend flag
765 */
766 public void setSuspend(boolean suspend) {
767
768 this.suspend = suspend;
769
770 }
771
772
773 // ------------------------------------------------- Event Listener Methods
774
775
776 /**
777 * Add a listener that is notified each time beans are added,
778 * replaced, or removed in this context.
779 *
780 * @param listener The ContextListener to be added
781 */
782 public void addContextListener(ContextListener listener) {
783
784 support.addContextListener(listener);
785
786 }
787
788
789 /**
790 * Remove a listener that is notified each time beans are added,
791 * replaced, or removed in this context.
792 *
793 * @param listener The ContextListener to be removed
794 */
795 public void removeContextListener(ContextListener listener) {
796
797 support.removeContextListener(listener);
798
799 }
800
801
802 // -------------------------------------------------------- Package Methods
803
804
805 /**
806 * Return the array of registered <code>Scope</code> names for this
807 * <code>Context</code>.
808 */
809 String[] getScopeNames() {
810
811 return (this.names);
812
813 }
814
815
816 }