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 }