001 package sale; 002 003 import java.io.*; 004 005 import users.User; 006 007 import sale.stdforms.MsgForm; 008 009 import data.DataBasket; 010 import data.Stock; 011 import data.Catalog; 012 013 import log.*; 014 015 /** 016 * A process. Processes are used to manipulate data in {@link DataBasket DataBaskets}, {@link Stock Stocks}, 017 * {@link Catalog Catalogs}, and any other data structures you might want to use. 018 * 019 * <p>Processes are viewed as finite deterministic automata, internally represented by a directed graph. The 020 * nodes of this graph are called {@link Gate Gates}, the edges are {@link Transition Transitions}.</p> 021 * 022 * <p>Processes are persistent, i.e. they are automatically restored when the system comes up again after 023 * having been down for some time.</p> 024 * 025 * <p>A process can be suspended whenever it is at a gate. When a process is asked to 026 * {@link #suspend suspend()} while it is in a transition, it will get suspended only at the next gate. For 027 * this to be feasible, transitions must be rather short, and, in particular, must not comprise any user 028 * interaction.</p> 029 * 030 * @author Steffen Zschaler 031 * @version 2.0 17/08/1999 032 * @since v2.0 033 */ 034 public abstract class SaleProcess implements LogContext, Loggable, ProcessErrorCodes, Serializable { 035 036 /** 037 * The name of this process. Used to identify the process, esp. for debug purposes. 038 * 039 * @serial 040 */ 041 private String m_sName; 042 043 /** 044 * The context in which this process runs. Used for all user communication, log and 045 * data access. 046 * 047 * @serial 048 */ 049 private ProcessContext m_pcContext; 050 051 /** 052 * The monitor synchronizing access to the process context. 053 */ 054 private transient Object m_oContextLock; 055 056 /** 057 * Return the monitor synchronizing access to the process context. 058 * 059 * @override Never 060 */ 061 private Object getContextLock() { 062 if (m_oContextLock == null) { 063 m_oContextLock = new Object(); 064 } 065 066 return m_oContextLock; 067 } 068 069 /** 070 * The DataBasket used to implement transactional behavior. 071 * 072 * @serial 073 */ 074 private DataBasket m_dbWorkBasket; 075 076 /** 077 * The previous log context of the current DataBasket. 078 * 079 * @serial 080 */ 081 private LogContext m_lcOldBasketContext; 082 083 /** 084 * The monitor synchronizing access to the DataBasket. 085 */ 086 private transient Object m_oBasketLock; 087 088 /** 089 * Return the monitor synchronizing access to the DataBasket. 090 * 091 * @override Never 092 */ 093 private Object getBasketLock() { 094 if (m_oBasketLock == null) { 095 m_oBasketLock = new Object(); 096 } 097 098 return m_oBasketLock; 099 } 100 101 /** 102 * The current gate, if any. 103 * 104 * @serial 105 */ 106 protected Gate m_gCurGate; 107 108 /** 109 * The current transition, if any. 110 * 111 * @serial 112 */ 113 protected Transition m_tCurTransition; 114 115 /** 116 * The process' main thread. 117 */ 118 private transient Thread m_trdMain = null; 119 120 /** 121 * Flag indicating whether the process is currently suspended. 122 * 123 * @serial 124 */ 125 private boolean m_fSuspended = false; 126 127 /** 128 * Flag indicating whether the process has been resumed. 129 * 130 * @serial 131 */ 132 private boolean m_fResumed = false; 133 134 /** 135 * Last error condition. 136 * 137 * @serial 138 */ 139 private int m_nErrorCode = 0; 140 141 /** 142 * Additional information concerning the cause of the last error. 143 * 144 * @serial <strong>Attention:</strong> This may be a common cause of mistake, when objects handed in as 145 * error descriptions are not serializable! 146 */ 147 private Object m_oErrorExtraInfo = null; 148 149 /** 150 * Count error nesting, so that we do not run into infinite loops. 151 * 152 * @serial 153 */ 154 private int m_nErrorNesting = 0; 155 156 /** 157 * Create a new SaleProcess with a given name. 158 * 159 * @param sName the name of this process. 160 */ 161 public SaleProcess(String sName) { 162 super(); 163 164 m_sName = sName; 165 } 166 167 /** 168 * Return the name of this process. 169 * 170 * @override Never 171 * 172 * @return the name of the process. 173 */ 174 public String getName() { 175 return m_sName; 176 } 177 178 /** 179 * Attach a ProcessContext to this process. 180 * 181 * @override Never 182 * 183 * @param pcNew the process context to be attached. 184 * 185 * @return the previously attached process context, if any. 186 */ 187 public ProcessContext attach(ProcessContext pcNew) { 188 synchronized (getContextLock()) { 189 ProcessContext pc = m_pcContext; 190 191 m_pcContext = pcNew; 192 193 if (isAlive()) { 194 // if the process is currently alive, we have to 195 // unregister the process with the old context... 196 if (pc != null) { 197 pc.processFinished(this); 198 } 199 200 //...and register it with the new context 201 if (m_pcContext != null) { 202 m_pcContext.processStarted(this); 203 } 204 } 205 206 return pc; 207 } 208 } 209 210 /** 211 * Detach and return the current process context. 212 * 213 * @override Never 214 */ 215 public ProcessContext detachContext() { 216 return attach((ProcessContext)null); 217 } 218 219 /** 220 * Return the process context attached to this process. 221 * 222 * @override Never 223 */ 224 public ProcessContext getContext() { 225 synchronized (getContextLock()) { 226 return m_pcContext; 227 } 228 } 229 230 /** 231 * Attach the DataBaskte that is going to be used to implement transactional 232 * behavior for this Process. 233 * 234 * @override Never 235 * 236 * @param dbNew the DataBasket to be attached. 237 * 238 * @return the previously attached DataBasket, if any. 239 */ 240 public DataBasket attach(DataBasket dbNew) { 241 synchronized (getBasketLock()) { 242 DataBasket db = m_dbWorkBasket; 243 244 if (m_dbWorkBasket != null) { 245 m_dbWorkBasket.setLogContext(m_lcOldBasketContext); 246 } 247 248 m_dbWorkBasket = dbNew; 249 250 if (m_dbWorkBasket != null) { 251 m_lcOldBasketContext = m_dbWorkBasket.setLogContext(this); 252 } 253 254 return db; 255 } 256 } 257 258 /** 259 * Detach and return the current DataBasket. 260 * 261 * @override Never 262 */ 263 public DataBasket detachBasket() { 264 return attach((DataBasket)null); 265 } 266 267 /** 268 * Get the currently attached DataBasket. 269 * 270 * @override Never 271 */ 272 public DataBasket getBasket() { 273 synchronized (getBasketLock()) { 274 return m_dbWorkBasket; 275 } 276 } 277 278 /** 279 * Return true if this Process can be stopped with a subsequent <code>quit()</code> 280 * command. 281 * 282 * @override Sometimes The default implementation will return <code>!fContextDestroy</code>, so that the 283 * process can only be quitted, if it will be possible to resume it afterwards. 284 * 285 * @param fContextDestroy true, if the quit request was issued due to a destroyal of 286 * the process' context. If false you can assume that it will be possible to restore 287 * and resume the Process after it had been <code>quit</code>ted. 288 * 289 * @return Currently returns <code>!fContextDestroy</code>, so that the process can 290 * only be quitted, if it will be possible to resume it afterwards. 291 */ 292 public boolean canQuit(boolean fContextDestroy) { 293 return!fContextDestroy; 294 } 295 296 /** 297 * Internal error helper, used to cancel a transition or gate when it calls error(). 298 * 299 * @author Steffen Zschaler 300 * @version 2.0 17/08/1999 301 * @since v2.0 302 */ 303 private class ProcessErrorError extends Error { 304 public ProcessErrorError() { 305 super(); 306 } 307 } 308 309 /** 310 * Raise an error in this process. 311 * 312 * <p>First calls {@link #printErrorInfo} to inform the user of the error condition, 313 * then cancels the process by jumping to the "error" gate.</p> 314 * 315 * <p><strong>Attention:</strong>This method must only be called from within a gate or 316 * a transition of this process. If called from any other environment, unpredictable 317 * behavior will result.</p> 318 * 319 * @override Never 320 * 321 * @param nErrorCode the error code. 322 */ 323 public void error(int nErrorCode) { 324 error(nErrorCode, (Object)null); 325 } 326 327 /** 328 * Raise an error in this process. 329 * 330 * <p>First calls {@link #printErrorInfo} to inform the user of the error condition, 331 * then cancels the process by jumping to the "error" gate.</p> 332 * 333 * <p><strong>Attention:</strong>This method must only be called from within a gate or 334 * a transition of this process. If called from any other environment, unpredictable 335 * behavior will result.</p> 336 * 337 * @override Never 338 * 339 * @param nErrorCode the error code. 340 * @param oExtraInfo additional information that explains the cause of the error. 341 */ 342 public void error(int nErrorCode, Object oExtraInfo) { 343 m_nErrorCode = nErrorCode; 344 m_oErrorExtraInfo = oExtraInfo; 345 m_nErrorNesting++; 346 347 //printErrorInfo (nErrorCode, oExtraInfo); --> called at the error gate! 348 349 throw new ProcessErrorError(); 350 } 351 352 /** 353 * Raise an error in this process. 354 * 355 * <p>First calls {@link #printErrorInfo} to inform the user of the error condition, 356 * then cancels the process by jumping to the "error" gate.</p> 357 * 358 * <p><strong>Attention:</strong>This method must only be called from within a gate or 359 * a transition of this process. If called from any other environment, unpredictable 360 * behavior will result.</p> 361 * 362 * @override Never 363 * 364 * @param nErrorCode the error code. 365 * @param tExtraInfo the exception that caused the error. 366 */ 367 public void error(int nErrorCode, Throwable tExtraInfo) { 368 369 java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); 370 java.io.PrintWriter pw = new java.io.PrintWriter(bos); 371 372 tExtraInfo.printStackTrace(pw); 373 374 pw.close(); 375 376 error(nErrorCode, bos.toString()); 377 } 378 379 /** 380 * Print error information to inform the user of an error condition. 381 * 382 * <p>Calls {@link #getErrorMsg} to resolve the error code into an error message. 383 * All occurences of "%o" in the error message are then replaced by the 384 * extra information's string representation.</p> 385 * 386 * <p>If the context has a {@link ProcessContext#hasUseableDisplay useable display} 387 * a {@link sale.stdforms.MsgForm} is displayed containing the error message. 388 * Otherwise, the error message is printed to the standard error stream.</p> 389 * 390 * <p>This method is never called directly, but rather called by the Framework as 391 * appropriate. Thus, it is assured, that user communication takes place at a gate 392 * only, even in the case of an error.</p> 393 * 394 * @override Never 395 * 396 * @param nErrorCode the error code. 397 * @param oExtraInfo additional information concerning the cause of the error. 398 */ 399 protected void printErrorInfo(int nErrorCode, Object oExtraInfo) { 400 String sErrorMsg = getErrorMsg(nErrorCode); 401 402 if (oExtraInfo != null) { 403 if (sErrorMsg.indexOf("%o") >= 0) { 404 int nIndex; 405 while ((nIndex = sErrorMsg.indexOf("%o")) >= 0) { 406 407 String sTemp = sErrorMsg.substring(0, nIndex) + oExtraInfo; 408 if (nIndex < sErrorMsg.length() - 2) { 409 sTemp += sErrorMsg.substring(nIndex + 2); 410 } 411 412 sErrorMsg = sTemp; 413 } 414 } else { 415 sErrorMsg += "\nAdditional Information: " + oExtraInfo; 416 } 417 } else { 418 if (sErrorMsg.indexOf("%o") >= 0) { 419 int nIndex; 420 421 while ((nIndex = sErrorMsg.indexOf("%o")) >= 0) { 422 423 String sTemp = sErrorMsg.substring(0, nIndex) + "<>"; 424 if (nIndex < sErrorMsg.length() - 2) { 425 sTemp += sErrorMsg.substring(nIndex + 2); 426 } 427 428 sErrorMsg = sTemp; 429 } 430 } 431 } 432 433 if ((m_pcContext != null) && (m_pcContext.hasUseableDisplay(this))) { 434 try { 435 m_pcContext.setFormSheet(this, new MsgForm("Error", sErrorMsg)); 436 } 437 catch (InterruptedException e) {} 438 } else { 439 System.err.println("Error in process <" + getName() + ">:\n"); 440 System.err.println(sErrorMsg); 441 } 442 } 443 444 /** 445 * Return a readable version of the error message. 446 * 447 * @override Sometimes Override this method whenever you define new error codes for a process. 448 * 449 * @param nErrorCode the error code. 450 * 451 * @return a readable version of the error message. 452 */ 453 public String getErrorMsg(int nErrorCode) { 454 switch (nErrorCode) { 455 case ERR_INTERNAL: 456 return "An internal error occured. " + "Please file a bug report to your programmer team.\n" + 457 "Error message (Please provide this message with your bug report):\n\n%o"; 458 case NOT_ENOUGH_ELEMENTS_ERROR: 459 return "Sorry, not enough elements."; 460 case REMOVE_VETO_EXCEPTION: 461 return "Sorry, couldn't delete."; 462 case DUPLICATE_KEY_EXCEPTION: 463 return "Element does already exist."; 464 case DATABASKET_CONFLICT_ERROR: 465 return "Your action stands in conflict with an action of another user."; 466 default: 467 return "Error no. " + nErrorCode + " occured.\nProcess cancelled."; 468 } 469 } 470 471 /** 472 * Suspend the process. 473 * 474 * <p>This method will suspend the process at the nearest gate. The method will block 475 * until the process was suspended.</p> 476 * 477 * @override Never 478 * 479 * @exception InterruptedException if an interrupt occurs in the calling thread while 480 * waiting for the process to suspend. 481 */ 482 public synchronized void suspend() throws InterruptedException { 483 if (m_fSuspended) { 484 // already done. 485 return; 486 } 487 488 m_fSuspended = true; 489 490 Thread trdMain = m_trdMain; 491 if (trdMain != null) { 492 trdMain.interrupt(); 493 494 trdMain.join(); // wait for main thread to finish 495 // allow InterruptedException, if any, to propagate upwards 496 } 497 } 498 499 /** 500 * Return true if this process is currently suspended. 501 * 502 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 503 * call it from within a gate or transition without fear for deadlocks, but there 504 * might be certain rare circumstances where the several <code>isXXX()</code> 505 * methods' return values give an seemingly inconsistent picture.</p> 506 * 507 * @override Never 508 */ 509 public final boolean isSuspended() { 510 return m_fSuspended; 511 } 512 513 /** 514 * Resume a previously suspended process. 515 * 516 * <p>This method will resume the process at the gate at which it was suspended.</p> 517 * 518 * @override Never 519 */ 520 public synchronized void resume() { 521 m_fResumed = true; 522 523 start(); 524 } 525 526 /** 527 * Return true if this process has been resumed from a previous suspended state. 528 * 529 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 530 * call it from within a gate or transition without fear for deadlocks, but there 531 * might be certain rare circumstances where the several <code>isXXX()</code> 532 * methods' return values give an seemingly inconsistent picture.</p> 533 * 534 * @override Never 535 */ 536 public final boolean isResumed() { 537 return m_fResumed; 538 } 539 540 /** 541 * Start the process. 542 * 543 * @override Never 544 */ 545 public synchronized void start() { 546 if (m_trdMain == null) { 547 m_fSuspended = false; 548 549 if (!m_fResumed) { 550 m_gCurGate = getInitialGate(); 551 } 552 553 m_trdMain = new Thread("SaleProcess thread: <" + getName() + ">.main()") { 554 public void run() { 555 main(); 556 } 557 }; 558 559 m_trdMain.start(); 560 } 561 } 562 563 /** 564 * Return true if this process has been started, but has not yet died. 565 * 566 * <p>In contrast to {@link #isRunning()} <code>isAlive()</code> will also 567 * return true for a process that has been suspended.</p> 568 * 569 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 570 * call it from within a gate or transition without fear for deadlocks, but there 571 * might be certain rare circumstances where the several <code>isXXX()</code> 572 * methods' return values give an seemingly inconsistent picture.</p> 573 * 574 * @override Never 575 */ 576 public final boolean isAlive() { 577 return isRunning() || isSuspended(); 578 } 579 580 /** 581 * Return true if this process is currently running. The process is running, if it 582 * has been started, but not yet stopped nor suspended. 583 * 584 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 585 * call it from within a gate or transition without fear for deadlocks, but there 586 * might be certain rare circumstances where the several <code>isXXX()</code> 587 * methods' return values give an seemingly inconsistent picture.</p> 588 * 589 * @override Never 590 */ 591 public final boolean isRunning() { 592 return (m_trdMain != null); 593 } 594 595 /** 596 * A thread providing an exception firewall for all subprocesses of a process. 597 */ 598 private class SubProcess extends Thread { 599 600 /** 601 * The activity to be guarded by the exception firewall. 602 */ 603 private Runnable m_rSubProcess; 604 605 /** 606 * The parent process. 607 */ 608 private SaleProcess m_pParent; 609 610 /** 611 * Create a new subprocess. 612 */ 613 public SubProcess(String sName, SaleProcess pParent, Runnable rSubProcess) { 614 super(sName); 615 616 m_pParent = pParent; 617 m_rSubProcess = rSubProcess; 618 } 619 620 /** 621 * The exception firewall. 622 */ 623 public void run() { 624 try { 625 m_rSubProcess.run(); 626 } 627 catch (ProcessErrorError pe) { 628 // silently stop the subprocess 629 } 630 catch (ThreadDeath td) { 631 throw td; 632 } 633 catch (Throwable t) { 634 try { 635 m_pParent.error(ERR_INTERNAL, t); 636 } 637 catch (ProcessErrorError pe) { 638 // silently stop the subprocess 639 } 640 } 641 } 642 } 643 644 /** 645 * The gate that is responsible for printing error messages. 646 */ 647 private class PrintErrorGate implements Gate { 648 649 private int m_nCode; 650 private Object m_oInfo; 651 private int m_nErrNesting; 652 653 public PrintErrorGate(int nErrorNesting) { 654 super(); 655 656 m_nCode = m_nErrorCode; 657 m_oInfo = m_oErrorExtraInfo; 658 m_nErrNesting = nErrorNesting; 659 } 660 661 public Transition getNextTransition(SaleProcess p, User u) { 662 printErrorInfo(m_nCode, m_oInfo); 663 664 return new GateChangeTransition(p.getErrorGate(m_nErrNesting)); 665 } 666 } 667 668 /** 669 * The central control loop of the process. 670 * 671 * @override Never 672 */ 673 private void main() { 674 675 int nCurErrorNesting = m_nErrorNesting; 676 677 try { 678 // initialization 679 if (!m_fResumed) { 680 m_pcContext.processStarted(this); 681 } 682 683 try { 684 onResumeOrStart(m_fResumed); 685 } 686 catch (ProcessErrorError pe) { 687 nCurErrorNesting = m_nErrorNesting; 688 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 689 } 690 691 // the actual control loop 692 while ((m_gCurGate != null) && (!m_fSuspended)) { 693 // Loop initialization 694 m_tCurTransition = null; 695 696 // Let the gate figure out the next Transition. 697 // This is interruptible. 698 Thread trdGate = new SubProcess("SaleProcess thread: <" + getName() + ">.gateHandler", this, 699 new Runnable() { 700 public void run() { 701 try { 702 m_tCurTransition = m_gCurGate.getNextTransition(SaleProcess.this, 703 ((m_pcContext != null) ? (m_pcContext.getCurrentUser(SaleProcess.this)) : (null))); 704 705 if (m_fSuspended) { 706 m_tCurTransition = null; 707 } 708 } 709 catch (InterruptedException e) { 710 util.Debug.print("Caught interrupt in Gate handler.", -1); 711 m_tCurTransition = null; 712 } 713 } 714 }); 715 716 trdGate.start(); 717 718 // We have to delay execution until trdGate has finished and returned the next transition. 719 // Otherwise we might end up having trdTransition (see below) executed before we have 720 // the correct value for m_tCurTransition 721 while (trdGate.isAlive()) { // zzz (see below!) 722 try { 723 trdGate.join(); //wait for trdGate to die 724 } 725 catch (InterruptedException e) { 726 util.Debug.print("Caught interrupt in main process handler.", -1); 727 if (m_fSuspended) { 728 util.Debug.print("In main process handler: Handing interrupt on to gate handler.", -1); 729 trdGate.interrupt(); 730 // we don't need to wait for trdGate to die here, as we will simply enter the loop at zzz again 731 } 732 } 733 } 734 735 util.Debug.print("In main process handler: Gate handler died.", -1); 736 737 if (m_fSuspended) { 738 // if the process was suspended, break the control loop. 739 break; 740 } 741 742 if (m_nErrorNesting != nCurErrorNesting) { 743 // an error occurred: jump to "error" gate. 744 nCurErrorNesting = m_nErrorNesting; 745 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 746 continue; 747 } 748 749 // Leave the gate and do a Transition. 750 // This is non-interruptible, except on error conditions. 751 Thread trdTransition = new SubProcess("SaleProcess thread: <" + getName() + 752 ">.transitionHandler", this, new Runnable() { 753 public void run() { 754 m_gCurGate = m_tCurTransition.perform(SaleProcess.this, 755 ((m_pcContext != null) ? (m_pcContext.getCurrentUser(SaleProcess.this)) : (null))); 756 } 757 }); 758 759 trdTransition.start(); 760 761 // wait for trdTransition to die before we create and execute the next trdGate 762 while (trdTransition.isAlive()) { 763 try { 764 trdTransition.join(); 765 } 766 catch (InterruptedException e) { 767 // In a Transition we don't want to be interrupted, so just go on waiting. 768 // The m_fSuspended flag will be set by the suspend() call. 769 continue; 770 } 771 } 772 773 if (m_nErrorNesting != nCurErrorNesting) { 774 // an error occurred: jump to "error" gate. 775 nCurErrorNesting = m_nErrorNesting; 776 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 777 778 continue; 779 } 780 } 781 782 if (m_fSuspended) { 783 // special cleanup on suspend() calls. 784 try { 785 onSuspended(); 786 } 787 catch (ProcessErrorError pe) { 788 nCurErrorNesting = m_nErrorNesting; 789 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 790 } 791 } 792 } 793 catch (Throwable t) { 794 System.err.println("Exception occured in process " + getName() + ":\n"); 795 t.printStackTrace(); 796 } 797 finally { 798 try { 799 onFinished(); 800 } 801 catch (ProcessErrorError pe) { 802 if (m_fSuspended) { 803 // on any error only jump to the "error" gate if the process has been suspended 804 // otherwise just forget about the error ! 805 nCurErrorNesting = m_nErrorNesting; 806 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 807 } 808 } 809 catch (ThreadDeath td) {} 810 catch (Throwable t) { 811 System.err.println("Exception occured in process " + getName() + ", onFinished() sequence:\n"); 812 t.printStackTrace(); 813 } 814 815 // make sure, this is always done 816 if (!isSuspended() && (m_pcContext != null)) { 817 m_pcContext.processFinished(this); 818 } 819 m_fResumed = false; 820 m_trdMain = null; 821 } 822 } 823 824 /** 825 * Hook method called on every start or resume of the process. Should perform any 826 * global process initializiation. 827 * 828 * <p>This method is called in the process' main thread and any uncaught exception 829 * raised in this method will lead to the process being stopped.</p> 830 * 831 * @override Sometimes Override this method if you need special initialization code. 832 * 833 * @param fIsResume true if the process has not been started afresh, but rather has 834 * been resumed. 835 */ 836 protected void onResumeOrStart(boolean fIsResume) {} 837 838 /** 839 * Hook method called whenever the process was suspended. This method is called 840 * in the process' main thread and any uncaught exception raised in this method 841 * will be reported from that thread.</p> 842 * 843 * <p>This method is called in the process' main thread and any uncaught exception 844 * raised in this method will lead to the process being stopped.</p> 845 * 846 * @override Sometimes Override this method if you need special cleanup code for suspended processes. 847 */ 848 protected void onSuspended() {} 849 850 /** 851 * Hook method called whenever the process was finished, independently of whether 852 * the process was really finished or just suspended. 853 * 854 * <p>You can find out whether the process was just suspended by calling 855 * {@link #isSuspended isSuspended()}.</p> 856 * 857 * <p>This method is called in the process' main thread and any uncaught exception 858 * raised in this method will be reported in this thread. This method must 859 * <strong>not</strong> call {@link #error error()}, however.</p> 860 * 861 * @override Sometimes Override this method if you need special cleanup code at the end of a process. 862 */ 863 protected void onFinished() {} 864 865 /** 866 * Quit the process at the nearest gate. The process will jump to the 867 * "quit" gate. 868 * 869 * <p><code>quit()</code> first calls <code>suspend()</code> then sets the current 870 * gate to be the "quit" gate and finally <code>resume()</code>-s the 871 * process.</p> 872 * 873 * @override Never 874 * 875 * @param fWaitQuit if true, quit will block until the process ended. 876 * 877 * @exception InterruptedException if an interrupt occured in the calling thread 878 * while waiting for the process to quit. 879 * 880 * @see #getQuitGate 881 */ 882 public synchronized void quit(boolean fWaitQuit) throws InterruptedException { 883 884 if (!isAlive()) { 885 return; 886 } 887 888 try { 889 suspend(); 890 } 891 catch (InterruptedException e) {} 892 893 m_gCurGate = getQuitGate(); 894 895 resume(); 896 897 if (fWaitQuit) { 898 // copy to avoid NullPointerExceptions 899 Thread trdMain = m_trdMain; 900 901 if (trdMain != null) { 902 // wait for the process to finish. 903 trdMain.join(); 904 } 905 } 906 } 907 908 /** 909 * A log entry describing a process that was executed. 910 * 911 * <p>The default implementation will only give the name of the process and when it was logged.</p> 912 * 913 * @author Steffen Zschaler 914 * @version 2.0 14/07/1999 915 * @since v2.0 916 */ 917 public static class ProcessLogEntry extends LogEntry { 918 919 /** 920 * The name of the process that this log entry describes. 921 * 922 * @serial 923 */ 924 protected String m_sProcessName; 925 926 /** 927 * Create a new ProcessLogEntry. 928 */ 929 public ProcessLogEntry(SaleProcess p) { 930 super(); 931 932 m_sProcessName = p.getName(); 933 } 934 935 /** 936 * Return the name of the process that this log entry describes. 937 * 938 * @override Never 939 */ 940 public String getProcessName() { 941 return m_sProcessName; 942 } 943 944 /** 945 * Return descriptive information for this LogEntry. 946 * 947 * @override Always 948 */ 949 public String toString() { 950 return "Process \"" + getProcessName() + "\" logged on " + getLogDate() + "."; 951 } 952 } 953 954 /** 955 * Return information that describes the process for logging purposes. 956 * 957 * @override Always The default implementation produces a log entry that will simply give the name of the 958 * process and the time when logging happened. 959 * 960 * @see ProcessLogEntry 961 */ 962 public LogEntry getLogData() { 963 return new ProcessLogEntry(this); 964 } 965 966 /** 967 * Logs the given data to a log file. If the process is in a process context, the data is logged to the 968 * process context's log file. Otherwise, the global log file is used. 969 * 970 * @override Sometimes Override if you want to personalize log entries from the DataBasket. 971 * 972 * @param la the event to be logged. 973 * 974 * @exception LogNoOutputStreamException if no OutputStream has been 975 * specified for the log file. 976 * @exception IOException if an IOException occurs when writing to the 977 * log file. 978 */ 979 public void log(Loggable la) throws LogNoOutputStreamException, IOException { 980 if (getContext() != null) { 981 getContext().log(this, la); 982 } else { 983 Log.getGlobalLog().log(la); 984 } 985 } 986 987 /** 988 * Return the gate at which the process currently stands or which it just left. 989 * 990 * @override Never 991 */ 992 public Gate getCurrentGate() { 993 return m_gCurGate; 994 } 995 996 /** 997 * Return the initial gate for this process. 998 * 999 * <p>By the time this method gets called, you can assume that the {@link #getBasket working basket} and the 1000 * {@link #getContext process context} have been properly initialized.</p> 1001 * 1002 * @override Always The process will start at the gate that is returned by this method. Therefore, 1003 * in order to do anything sensible, you must override this method. 1004 */ 1005 protected abstract Gate getInitialGate(); 1006 1007 /** 1008 * Return the gate to jump to when quitting the process. 1009 * 1010 * <p>Transitions starting at this gate will usually perform a rollback and will then 1011 * jump to the "stop" gate.</p> 1012 * 1013 * @override Sometimes As a default, returns the "rollback" gate. 1014 * 1015 * @see #getStopGate 1016 * @see #getRollbackGate 1017 */ 1018 public Gate getQuitGate() { 1019 return getRollbackGate(); 1020 } 1021 1022 /** 1023 * Return the gate to jump to when an {@link #error error} occurs. 1024 * 1025 * <p>Transition starting at this gate can perform any specific error handling and 1026 * should then arrive at the "rollback" gate.</p> 1027 * 1028 * <p>When this method is called, {@link #getCurrentGate} will still deliver the 1029 * last valid gate.</p> 1030 * 1031 * @override Sometimes As a default returns the "rollback" gate, unless nErrorNesting is greater 1032 * than 1, in which case <code>null</code> is returned to indicate the end of the process. 1033 * 1034 * @param nErrorNesting a value that indicates nested errors. This value increases with 1035 * every new error, so that values greater than 1 indicate errors that occured while 1036 * other errors where handled. 1037 * 1038 * @return As a default returns the "rollback" gate, unless nErrorNesting is 1039 * greater than 1, in which case <code>null</code> is returned to indicate the end of 1040 * the process. 1041 */ 1042 protected Gate getErrorGate(int nErrorNesting) { 1043 if (nErrorNesting <= 1) { 1044 return getRollbackGate(); 1045 } else { 1046 return null; 1047 } 1048 } 1049 1050 /** 1051 * Return the gate to jump to when performing a rollback. 1052 * 1053 * <p>Transitions starting from this gate must roll back any data structures the process used.</p> 1054 * 1055 * @override Sometimes As a default returns a gate with a transition that will roll back the DataBasket 1056 * attached to the process and will eventually jump to the {@link #getLogGate "log" gate}. 1057 * 1058 * @see #getStopGate 1059 */ 1060 public Gate getRollbackGate() { 1061 return new Gate() { 1062 public Transition getNextTransition(SaleProcess p, User u) { 1063 return new Transition() { 1064 public Gate perform(SaleProcess p, User u) { 1065 DataBasket db = p.getBasket(); 1066 1067 if (db != null) { 1068 db.rollback(); 1069 } 1070 1071 return p.getLogGate(); 1072 } 1073 }; 1074 } 1075 }; 1076 } 1077 1078 /** 1079 * Return the gate to jump to when performing a commit. 1080 * 1081 * <p>Transitions starting from this gate must commit any data structures the process used.</p> 1082 * 1083 * @override Sometimes As a default returns a gate with a transition that will commit the DataBasket 1084 * attached to the process and will eventually jump to the {@link #getLogGate "log" gate}. 1085 * 1086 * @see #getStopGate 1087 */ 1088 public Gate getCommitGate() { 1089 return new Gate() { 1090 public Transition getNextTransition(SaleProcess p, User u) { 1091 return new Transition() { 1092 public Gate perform(SaleProcess p, User u) { 1093 DataBasket db = p.getBasket(); 1094 1095 if (db != null) { 1096 db.commit(); 1097 } 1098 1099 return p.getLogGate(); 1100 } 1101 }; 1102 } 1103 }; 1104 } 1105 1106 /** 1107 * Return the gate that the process must jump to if it wishes to be logged before finishing. 1108 * 1109 * <p>Transitions from this gate should {@link Log#log log} the process into a log file of their choice and 1110 * then proceed to the {@link #getStopGate "stop" gate}.</p> 1111 * 1112 * @override Sometimes As a default returns a gate with a transition that will log the process using the 1113 * process context's {@link ProcessContext#log log()} method.</p> 1114 */ 1115 public Gate getLogGate() { 1116 return new Gate() { 1117 public Transition getNextTransition(SaleProcess p, User u) { 1118 return new Transition() { 1119 public Gate perform(SaleProcess p, User u) { 1120 try { 1121 p.log(p); 1122 } 1123 catch (java.io.IOException ioe) { 1124 throw new Error("Exception occurred while logging process: " + ioe); 1125 } 1126 1127 return p.getStopGate(); 1128 } 1129 }; 1130 } 1131 }; 1132 } 1133 1134 /** 1135 * Return the last gate that this process should be at. 1136 * 1137 * <p> Transitions from this gate must return <code>null</code> instead of a next gate.</p> 1138 * 1139 * @override Sometimes As a default just return <code>null</code>, indicating no further processing is to be 1140 * performed.</p> 1141 */ 1142 public Gate getStopGate() { 1143 return null; 1144 } 1145 1146 /** 1147 * A LogEntryFilter that will accept only such {@link LogEntry LogEntries} that stem from a process. 1148 */ 1149 public static final LogEntryFilter LOGENTRYFILTER_PROCESSES_ONLY = new LogEntryFilter() { 1150 public boolean accept(LogEntry le) { 1151 return (le instanceof ProcessLogEntry); 1152 } 1153 }; 1154 }