001    /*
002     * Copyright 1999-2001,2004 The Apache Software Foundation.
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     */ 
016    
017    package org.apache.commons.workflow.base;
018    
019    
020    import java.util.EmptyStackException;
021    import org.apache.commons.collections.ArrayStack;
022    import org.apache.commons.jxpath.JXPathContext;
023    import org.apache.commons.workflow.Activity;
024    import org.apache.commons.workflow.Block;
025    import org.apache.commons.workflow.BlockState;
026    import org.apache.commons.workflow.Context;
027    import org.apache.commons.workflow.ContextEvent;
028    import org.apache.commons.workflow.ContextListener;
029    import org.apache.commons.workflow.Owner;
030    import org.apache.commons.workflow.Step;
031    import org.apache.commons.workflow.StepException;
032    import org.apache.commons.workflow.Scope;
033    import org.apache.commons.workflow.util.ContextSupport;
034    
035    
036    /**
037     * <strong>BaseContext</strong> is a basic <code>Context</code> implementation
038     * that can serve as a convenient base class for more sophisticated
039     * <code>Context</code> implementations.
040     *
041     * <p><strong>WARNING</strong> - No synchronization is performed within this
042     * class.  If it is used in a multiple thread environment, callers must
043     * take suitable precations.</p>
044     *
045     * @version $Revision: 155475 $ $Date: 2005-02-26 13:31:11 +0000 (Sat, 26 Feb 2005) $
046     * @author Craig R. McClanahan
047     */
048    
049    public class BaseContext implements Context {
050    
051    
052    
053        // ----------------------------------------------------------- Constructors
054    
055    
056        /**
057         * Construct a new BaseContext with all default values.
058         */
059        public BaseContext() {
060    
061            super();
062            names[LOCAL_SCOPE] = "local";
063            scopes[LOCAL_SCOPE] = new BaseScope();
064    
065        }
066    
067    
068        // ----------------------------------------------------- Instance Variables
069    
070    
071        /**
072         * The <code>Activity</code> that we are associated with and executing
073         * <code>Steps</code> from (if any).
074         */
075        protected Activity activity = null;
076    
077    
078        /**
079         * The bean representing the information about this Context made visible
080         * through JXPath.
081         */
082        protected BaseContextBean bean = null;
083    
084    
085        /**
086         * The suspended "next step" Step for each in-progress Activity that has
087         * issued a <code>call()</code> to execute a subordinate Activity.
088         */
089        protected ArrayStack calls = new ArrayStack();
090    
091    
092        /**
093         * The set of names associated with the registered <code>Scopes</code>.
094         */
095        protected String names[] = new String[MAX_SCOPES];
096    
097    
098        /**
099         * 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    }