View Javadoc

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 }