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 private static final long serialVersionUID = 8571800147640843469L; 305 306 public ProcessErrorError() { 307 super(); 308 } 309 } 310 311 /** 312 * Raise an error in this process. 313 * 314 * <p>First calls {@link #printErrorInfo} to inform the user of the error condition, 315 * then cancels the process by jumping to the "error" gate.</p> 316 * 317 * <p><strong>Attention:</strong>This method must only be called from within a gate or 318 * a transition of this process. If called from any other environment, unpredictable 319 * behavior will result.</p> 320 * 321 * @override Never 322 * 323 * @param nErrorCode the error code. 324 */ 325 public void error(int nErrorCode) { 326 error(nErrorCode, (Object)null); 327 } 328 329 /** 330 * Raise an error in this process. 331 * 332 * <p>First calls {@link #printErrorInfo} to inform the user of the error condition, 333 * then cancels the process by jumping to the "error" gate.</p> 334 * 335 * <p><strong>Attention:</strong>This method must only be called from within a gate or 336 * a transition of this process. If called from any other environment, unpredictable 337 * behavior will result.</p> 338 * 339 * @override Never 340 * 341 * @param nErrorCode the error code. 342 * @param oExtraInfo additional information that explains the cause of the error. 343 */ 344 public void error(int nErrorCode, Object oExtraInfo) { 345 m_nErrorCode = nErrorCode; 346 m_oErrorExtraInfo = oExtraInfo; 347 m_nErrorNesting++; 348 349 //printErrorInfo (nErrorCode, oExtraInfo); --> called at the error gate! 350 351 throw new ProcessErrorError(); 352 } 353 354 /** 355 * Raise an error in this process. 356 * 357 * <p>First calls {@link #printErrorInfo} to inform the user of the error condition, 358 * then cancels the process by jumping to the "error" gate.</p> 359 * 360 * <p><strong>Attention:</strong>This method must only be called from within a gate or 361 * a transition of this process. If called from any other environment, unpredictable 362 * behavior will result.</p> 363 * 364 * @override Never 365 * 366 * @param nErrorCode the error code. 367 * @param tExtraInfo the exception that caused the error. 368 */ 369 public void error(int nErrorCode, Throwable tExtraInfo) { 370 371 java.io.ByteArrayOutputStream bos = new java.io.ByteArrayOutputStream(); 372 java.io.PrintWriter pw = new java.io.PrintWriter(bos); 373 374 tExtraInfo.printStackTrace(pw); 375 376 pw.close(); 377 378 error(nErrorCode, bos.toString()); 379 } 380 381 /** 382 * Print error information to inform the user of an error condition. 383 * 384 * <p>Calls {@link #getErrorMsg} to resolve the error code into an error message. 385 * All occurences of "%o" in the error message are then replaced by the 386 * extra information's string representation.</p> 387 * 388 * <p>If the context has a {@link ProcessContext#hasUseableDisplay useable display} 389 * a {@link sale.stdforms.MsgForm} is displayed containing the error message. 390 * Otherwise, the error message is printed to the standard error stream.</p> 391 * 392 * <p>This method is never called directly, but rather called by the Framework as 393 * appropriate. Thus, it is assured, that user communication takes place at a gate 394 * only, even in the case of an error.</p> 395 * 396 * @override Never 397 * 398 * @param nErrorCode the error code. 399 * @param oExtraInfo additional information concerning the cause of the error. 400 */ 401 protected void printErrorInfo(int nErrorCode, Object oExtraInfo) { 402 String sErrorMsg = getErrorMsg(nErrorCode); 403 404 if (oExtraInfo != null) { 405 if (sErrorMsg.indexOf("%o") >= 0) { 406 int nIndex; 407 while ((nIndex = sErrorMsg.indexOf("%o")) >= 0) { 408 409 String sTemp = sErrorMsg.substring(0, nIndex) + oExtraInfo; 410 if (nIndex < sErrorMsg.length() - 2) { 411 sTemp += sErrorMsg.substring(nIndex + 2); 412 } 413 414 sErrorMsg = sTemp; 415 } 416 } else { 417 sErrorMsg += "\nAdditional Information: " + oExtraInfo; 418 } 419 } else { 420 if (sErrorMsg.indexOf("%o") >= 0) { 421 int nIndex; 422 423 while ((nIndex = sErrorMsg.indexOf("%o")) >= 0) { 424 425 String sTemp = sErrorMsg.substring(0, nIndex) + "<>"; 426 if (nIndex < sErrorMsg.length() - 2) { 427 sTemp += sErrorMsg.substring(nIndex + 2); 428 } 429 430 sErrorMsg = sTemp; 431 } 432 } 433 } 434 435 if ((m_pcContext != null) && (m_pcContext.hasUseableDisplay(this))) { 436 try { 437 m_pcContext.setFormSheet(this, new MsgForm("Error", sErrorMsg)); 438 } 439 catch (InterruptedException e) {} 440 } else { 441 System.err.println("Error in process <" + getName() + ">:\n"); 442 System.err.println(sErrorMsg); 443 } 444 } 445 446 /** 447 * Return a readable version of the error message. 448 * 449 * @override Sometimes Override this method whenever you define new error codes for a process. 450 * 451 * @param nErrorCode the error code. 452 * 453 * @return a readable version of the error message. 454 */ 455 public String getErrorMsg(int nErrorCode) { 456 switch (nErrorCode) { 457 case ERR_INTERNAL: 458 return "An internal error occured. " + "Please file a bug report to your programmer team.\n" + 459 "Error message (Please provide this message with your bug report):\n\n%o"; 460 case NOT_ENOUGH_ELEMENTS_ERROR: 461 return "Sorry, not enough elements."; 462 case REMOVE_VETO_EXCEPTION: 463 return "Sorry, couldn't delete."; 464 case DUPLICATE_KEY_EXCEPTION: 465 return "Element does already exist."; 466 case DATABASKET_CONFLICT_ERROR: 467 return "Your action stands in conflict with an action of another user."; 468 default: 469 return "Error no. " + nErrorCode + " occured.\nProcess cancelled."; 470 } 471 } 472 473 /** 474 * Suspend the process. 475 * 476 * <p>This method will suspend the process at the nearest gate. The method will block 477 * until the process was suspended.</p> 478 * 479 * @override Never 480 * 481 * @exception InterruptedException if an interrupt occurs in the calling thread while 482 * waiting for the process to suspend. 483 */ 484 public synchronized void suspend() throws InterruptedException { 485 if (m_fSuspended) { 486 // already done. 487 return; 488 } 489 490 m_fSuspended = true; 491 492 Thread trdMain = m_trdMain; 493 if (trdMain != null) { 494 trdMain.interrupt(); 495 496 trdMain.join(); // wait for main thread to finish 497 // allow InterruptedException, if any, to propagate upwards 498 } 499 } 500 501 /** 502 * Return true if this process is currently suspended. 503 * 504 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 505 * call it from within a gate or transition without fear for deadlocks, but there 506 * might be certain rare circumstances where the several <code>isXXX()</code> 507 * methods' return values give an seemingly inconsistent picture.</p> 508 * 509 * @override Never 510 */ 511 public final boolean isSuspended() { 512 return m_fSuspended; 513 } 514 515 /** 516 * Resume a previously suspended process. 517 * 518 * <p>This method will resume the process at the gate at which it was suspended.</p> 519 * 520 * @override Never 521 */ 522 public synchronized void resume() { 523 m_fResumed = true; 524 525 start(); 526 } 527 528 /** 529 * Return true if this process has been resumed from a previous suspended state. 530 * 531 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 532 * call it from within a gate or transition without fear for deadlocks, but there 533 * might be certain rare circumstances where the several <code>isXXX()</code> 534 * methods' return values give an seemingly inconsistent picture.</p> 535 * 536 * @override Never 537 */ 538 public final boolean isResumed() { 539 return m_fResumed; 540 } 541 542 /** 543 * Start the process. 544 * 545 * @override Never 546 */ 547 public synchronized void start() { 548 if (m_trdMain == null) { 549 m_fSuspended = false; 550 551 if (!m_fResumed) { 552 m_gCurGate = getInitialGate(); 553 } 554 555 m_trdMain = new Thread("SaleProcess thread: <" + getName() + ">.main()") { 556 public void run() { 557 main(); 558 } 559 }; 560 561 m_trdMain.start(); 562 } 563 } 564 565 /** 566 * Return true if this process has been started, but has not yet died. 567 * 568 * <p>In contrast to {@link #isRunning()} <code>isAlive()</code> will also 569 * return true for a process that has been suspended.</p> 570 * 571 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 572 * call it from within a gate or transition without fear for deadlocks, but there 573 * might be certain rare circumstances where the several <code>isXXX()</code> 574 * methods' return values give an seemingly inconsistent picture.</p> 575 * 576 * @override Never 577 */ 578 public final boolean isAlive() { 579 return isRunning() || isSuspended(); 580 } 581 582 /** 583 * Return true if this process is currently running. The process is running, if it 584 * has been started, but not yet stopped nor suspended. 585 * 586 * <p><strong>Attention:</strong>This method is not synchronized. This allows you to 587 * call it from within a gate or transition without fear for deadlocks, but there 588 * might be certain rare circumstances where the several <code>isXXX()</code> 589 * methods' return values give an seemingly inconsistent picture.</p> 590 * 591 * @override Never 592 */ 593 public final boolean isRunning() { 594 return (m_trdMain != null); 595 } 596 597 /** 598 * A thread providing an exception firewall for all subprocesses of a process. 599 */ 600 private class SubProcess extends Thread { 601 602 /** 603 * The activity to be guarded by the exception firewall. 604 */ 605 private Runnable m_rSubProcess; 606 607 /** 608 * The parent process. 609 */ 610 private SaleProcess m_pParent; 611 612 /** 613 * Create a new subprocess. 614 */ 615 public SubProcess(String sName, SaleProcess pParent, Runnable rSubProcess) { 616 super(sName); 617 618 m_pParent = pParent; 619 m_rSubProcess = rSubProcess; 620 } 621 622 /** 623 * The exception firewall. 624 */ 625 public void run() { 626 try { 627 m_rSubProcess.run(); 628 } 629 catch (ProcessErrorError pe) { 630 // silently stop the subprocess 631 } 632 catch (ThreadDeath td) { 633 throw td; 634 } 635 catch (Throwable t) { 636 try { 637 m_pParent.error(ERR_INTERNAL, t); 638 } 639 catch (ProcessErrorError pe) { 640 // silently stop the subprocess 641 } 642 } 643 } 644 } 645 646 /** 647 * The gate that is responsible for printing error messages. 648 */ 649 private class PrintErrorGate implements Gate { 650 651 private static final long serialVersionUID = 17984935729494772L; 652 private int m_nCode; 653 private Object m_oInfo; 654 private int m_nErrNesting; 655 656 public PrintErrorGate(int nErrorNesting) { 657 super(); 658 659 m_nCode = m_nErrorCode; 660 m_oInfo = m_oErrorExtraInfo; 661 m_nErrNesting = nErrorNesting; 662 } 663 664 public Transition getNextTransition(SaleProcess p, User u) { 665 printErrorInfo(m_nCode, m_oInfo); 666 667 return new GateChangeTransition(p.getErrorGate(m_nErrNesting)); 668 } 669 } 670 671 /** 672 * The central control loop of the process. 673 * 674 * @override Never 675 */ 676 private void main() { 677 678 int nCurErrorNesting = m_nErrorNesting; 679 680 try { 681 // initialization 682 if (!m_fResumed) { 683 m_pcContext.processStarted(this); 684 } 685 686 try { 687 onResumeOrStart(m_fResumed); 688 } 689 catch (ProcessErrorError pe) { 690 nCurErrorNesting = m_nErrorNesting; 691 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 692 } 693 694 // the actual control loop 695 while ((m_gCurGate != null) && (!m_fSuspended)) { 696 // Loop initialization 697 m_tCurTransition = null; 698 699 // Let the gate figure out the next Transition. 700 // This is interruptible. 701 Thread trdGate = new SubProcess("SaleProcess thread: <" + getName() + ">.gateHandler", this, 702 new Runnable() { 703 public void run() { 704 try { 705 m_tCurTransition = m_gCurGate.getNextTransition(SaleProcess.this, 706 ((m_pcContext != null) ? (m_pcContext.getCurrentUser(SaleProcess.this)) : (null))); 707 708 if (m_fSuspended) { 709 m_tCurTransition = null; 710 } 711 } 712 catch (InterruptedException e) { 713 util.Debug.print("Caught interrupt in Gate handler.", -1); 714 m_tCurTransition = null; 715 } 716 } 717 }); 718 719 trdGate.start(); 720 721 // We have to delay execution until trdGate has finished and returned the next transition. 722 // Otherwise we might end up having trdTransition (see below) executed before we have 723 // the correct value for m_tCurTransition 724 while (trdGate.isAlive()) { // zzz (see below!) 725 try { 726 trdGate.join(); //wait for trdGate to die 727 } 728 catch (InterruptedException e) { 729 util.Debug.print("Caught interrupt in main process handler.", -1); 730 if (m_fSuspended) { 731 util.Debug.print("In main process handler: Handing interrupt on to gate handler.", -1); 732 trdGate.interrupt(); 733 // we don't need to wait for trdGate to die here, as we will simply enter the loop at zzz again 734 } 735 } 736 } 737 738 util.Debug.print("In main process handler: Gate handler died.", -1); 739 740 if (m_fSuspended) { 741 // if the process was suspended, break the control loop. 742 break; 743 } 744 745 if (m_nErrorNesting != nCurErrorNesting) { 746 // an error occurred: jump to "error" gate. 747 nCurErrorNesting = m_nErrorNesting; 748 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 749 continue; 750 } 751 752 // Leave the gate and do a Transition. 753 // This is non-interruptible, except on error conditions. 754 Thread trdTransition = new SubProcess("SaleProcess thread: <" + getName() + 755 ">.transitionHandler", this, new Runnable() { 756 public void run() { 757 m_gCurGate = m_tCurTransition.perform(SaleProcess.this, 758 ((m_pcContext != null) ? (m_pcContext.getCurrentUser(SaleProcess.this)) : (null))); 759 } 760 }); 761 762 trdTransition.start(); 763 764 // wait for trdTransition to die before we create and execute the next trdGate 765 while (trdTransition.isAlive()) { 766 try { 767 trdTransition.join(); 768 } 769 catch (InterruptedException e) { 770 // In a Transition we don't want to be interrupted, so just go on waiting. 771 // The m_fSuspended flag will be set by the suspend() call. 772 continue; 773 } 774 } 775 776 if (m_nErrorNesting != nCurErrorNesting) { 777 // an error occurred: jump to "error" gate. 778 nCurErrorNesting = m_nErrorNesting; 779 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 780 781 continue; 782 } 783 } 784 785 if (m_fSuspended) { 786 // special cleanup on suspend() calls. 787 try { 788 onSuspended(); 789 } 790 catch (ProcessErrorError pe) { 791 nCurErrorNesting = m_nErrorNesting; 792 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 793 } 794 } 795 } 796 catch (Throwable t) { 797 System.err.println("Exception occured in process " + getName() + ":\n"); 798 t.printStackTrace(); 799 } 800 finally { 801 try { 802 onFinished(); 803 } 804 catch (ProcessErrorError pe) { 805 if (m_fSuspended) { 806 // on any error only jump to the "error" gate if the process has been suspended 807 // otherwise just forget about the error ! 808 nCurErrorNesting = m_nErrorNesting; 809 m_gCurGate = new PrintErrorGate(m_nErrorNesting); 810 } 811 } 812 catch (ThreadDeath td) {} 813 catch (Throwable t) { 814 System.err.println("Exception occured in process " + getName() + ", onFinished() sequence:\n"); 815 t.printStackTrace(); 816 } 817 818 // make sure, this is always done 819 if (!isSuspended() && (m_pcContext != null)) { 820 m_pcContext.processFinished(this); 821 } 822 m_fResumed = false; 823 m_trdMain = null; 824 } 825 } 826 827 /** 828 * Hook method called on every start or resume of the process. Should perform any 829 * global process initializiation. 830 * 831 * <p>This method is called in the process' main thread and any uncaught exception 832 * raised in this method will lead to the process being stopped.</p> 833 * 834 * @override Sometimes Override this method if you need special initialization code. 835 * 836 * @param fIsResume true if the process has not been started afresh, but rather has 837 * been resumed. 838 */ 839 protected void onResumeOrStart(boolean fIsResume) {} 840 841 /** 842 * Hook method called whenever the process was suspended. This method is called 843 * in the process' main thread and any uncaught exception raised in this method 844 * will be reported from that thread.</p> 845 * 846 * <p>This method is called in the process' main thread and any uncaught exception 847 * raised in this method will lead to the process being stopped.</p> 848 * 849 * @override Sometimes Override this method if you need special cleanup code for suspended processes. 850 */ 851 protected void onSuspended() {} 852 853 /** 854 * Hook method called whenever the process was finished, independently of whether 855 * the process was really finished or just suspended. 856 * 857 * <p>You can find out whether the process was just suspended by calling 858 * {@link #isSuspended isSuspended()}.</p> 859 * 860 * <p>This method is called in the process' main thread and any uncaught exception 861 * raised in this method will be reported in this thread. This method must 862 * <strong>not</strong> call {@link #error error()}, however.</p> 863 * 864 * @override Sometimes Override this method if you need special cleanup code at the end of a process. 865 */ 866 protected void onFinished() {} 867 868 /** 869 * Quit the process at the nearest gate. The process will jump to the 870 * "quit" gate. 871 * 872 * <p><code>quit()</code> first calls <code>suspend()</code> then sets the current 873 * gate to be the "quit" gate and finally <code>resume()</code>-s the 874 * process.</p> 875 * 876 * @override Never 877 * 878 * @param fWaitQuit if true, quit will block until the process ended. 879 * 880 * @exception InterruptedException if an interrupt occured in the calling thread 881 * while waiting for the process to quit. 882 * 883 * @see #getQuitGate 884 */ 885 public synchronized void quit(boolean fWaitQuit) throws InterruptedException { 886 887 if (!isAlive()) { 888 return; 889 } 890 891 try { 892 suspend(); 893 } 894 catch (InterruptedException e) {} 895 896 m_gCurGate = getQuitGate(); 897 898 resume(); 899 900 if (fWaitQuit) { 901 // copy to avoid NullPointerExceptions 902 Thread trdMain = m_trdMain; 903 904 if (trdMain != null) { 905 // wait for the process to finish. 906 trdMain.join(); 907 } 908 } 909 } 910 911 /** 912 * A log entry describing a process that was executed. 913 * 914 * <p>The default implementation will only give the name of the process and when it was logged.</p> 915 * 916 * @author Steffen Zschaler 917 * @version 2.0 14/07/1999 918 * @since v2.0 919 */ 920 public static class ProcessLogEntry extends LogEntry { 921 922 /** 923 * ID for serialization. 924 */ 925 private static final long serialVersionUID = -1713298293459233904L; 926 927 /** 928 * The name of the process that this log entry describes. 929 * 930 * @serial 931 */ 932 protected String m_sProcessName; 933 934 /** 935 * Create a new ProcessLogEntry. 936 */ 937 public ProcessLogEntry(SaleProcess p) { 938 super(); 939 940 m_sProcessName = p.getName(); 941 } 942 943 /** 944 * Return the name of the process that this log entry describes. 945 * 946 * @override Never 947 */ 948 public String getProcessName() { 949 return m_sProcessName; 950 } 951 952 /** 953 * Return descriptive information for this LogEntry. 954 * 955 * @override Always 956 */ 957 public String toString() { 958 return "Process \"" + getProcessName() + "\" logged on " + getLogDate() + "."; 959 } 960 } 961 962 /** 963 * Return information that describes the process for logging purposes. 964 * 965 * @override Always The default implementation produces a log entry that will simply give the name of the 966 * process and the time when logging happened. 967 * 968 * @see ProcessLogEntry 969 */ 970 public LogEntry getLogData() { 971 return new ProcessLogEntry(this); 972 } 973 974 /** 975 * Logs the given data to a log file. If the process is in a process context, the data is logged to the 976 * process context's log file. Otherwise, the global log file is used. 977 * 978 * @override Sometimes Override if you want to personalize log entries from the DataBasket. 979 * 980 * @param la the event to be logged. 981 * 982 * @exception LogNoOutputStreamException if no OutputStream has been 983 * specified for the log file. 984 * @exception IOException if an IOException occurs when writing to the 985 * log file. 986 */ 987 public void log(Loggable la) throws LogNoOutputStreamException, IOException { 988 if (getContext() != null) { 989 getContext().log(this, la); 990 } else { 991 Log.getGlobalLog().log(la); 992 } 993 } 994 995 /** 996 * Return the gate at which the process currently stands or which it just left. 997 * 998 * @override Never 999 */ 1000 public Gate getCurrentGate() { 1001 return m_gCurGate; 1002 } 1003 1004 /** 1005 * Return the initial gate for this process. 1006 * 1007 * <p>By the time this method gets called, you can assume that the {@link #getBasket working basket} and the 1008 * {@link #getContext process context} have been properly initialized.</p> 1009 * 1010 * @override Always The process will start at the gate that is returned by this method. Therefore, 1011 * in order to do anything sensible, you must override this method. 1012 */ 1013 protected abstract Gate getInitialGate(); 1014 1015 /** 1016 * Return the gate to jump to when quitting the process. 1017 * 1018 * <p>Transitions starting at this gate will usually perform a rollback and will then 1019 * jump to the "stop" gate.</p> 1020 * 1021 * @override Sometimes As a default, returns the "rollback" gate. 1022 * 1023 * @see #getStopGate 1024 * @see #getRollbackGate 1025 */ 1026 public Gate getQuitGate() { 1027 return getRollbackGate(); 1028 } 1029 1030 /** 1031 * Return the gate to jump to when an {@link #error error} occurs. 1032 * 1033 * <p>Transition starting at this gate can perform any specific error handling and 1034 * should then arrive at the "rollback" gate.</p> 1035 * 1036 * <p>When this method is called, {@link #getCurrentGate} will still deliver the 1037 * last valid gate.</p> 1038 * 1039 * @override Sometimes As a default returns the "rollback" gate, unless nErrorNesting is greater 1040 * than 1, in which case <code>null</code> is returned to indicate the end of the process. 1041 * 1042 * @param nErrorNesting a value that indicates nested errors. This value increases with 1043 * every new error, so that values greater than 1 indicate errors that occured while 1044 * other errors where handled. 1045 * 1046 * @return As a default returns the "rollback" gate, unless nErrorNesting is 1047 * greater than 1, in which case <code>null</code> is returned to indicate the end of 1048 * the process. 1049 */ 1050 protected Gate getErrorGate(int nErrorNesting) { 1051 if (nErrorNesting <= 1) { 1052 return getRollbackGate(); 1053 } else { 1054 return null; 1055 } 1056 } 1057 1058 /** 1059 * Return the gate to jump to when performing a rollback. 1060 * 1061 * <p>Transitions starting from this gate must roll back any data structures the process used.</p> 1062 * 1063 * @override Sometimes As a default returns a gate with a transition that will roll back the DataBasket 1064 * attached to the process and will eventually jump to the {@link #getLogGate "log" gate}. 1065 * 1066 * @see #getStopGate 1067 */ 1068 public Gate getRollbackGate() { 1069 return new Gate() { 1070 private static final long serialVersionUID = 252941870044050473L; 1071 1072 public Transition getNextTransition(SaleProcess p, User u) { 1073 return new Transition() { 1074 private static final long serialVersionUID = 1547372730944858235L; 1075 1076 public Gate perform(SaleProcess p, User u) { 1077 DataBasket db = p.getBasket(); 1078 1079 if (db != null) { 1080 db.rollback(); 1081 } 1082 1083 return p.getLogGate(); 1084 } 1085 }; 1086 } 1087 }; 1088 } 1089 1090 /** 1091 * Return the gate to jump to when performing a commit. 1092 * 1093 * <p>Transitions starting from this gate must commit any data structures the process used.</p> 1094 * 1095 * @override Sometimes As a default returns a gate with a transition that will commit the DataBasket 1096 * attached to the process and will eventually jump to the {@link #getLogGate "log" gate}. 1097 * 1098 * @see #getStopGate 1099 */ 1100 public Gate getCommitGate() { 1101 return new Gate() { 1102 private static final long serialVersionUID = -6840027684897796460L; 1103 1104 public Transition getNextTransition(SaleProcess p, User u) { 1105 return new Transition() { 1106 private static final long serialVersionUID = -5152472793320498286L; 1107 1108 public Gate perform(SaleProcess p, User u) { 1109 DataBasket db = p.getBasket(); 1110 1111 if (db != null) { 1112 db.commit(); 1113 } 1114 1115 return p.getLogGate(); 1116 } 1117 }; 1118 } 1119 }; 1120 } 1121 1122 /** 1123 * Return the gate that the process must jump to if it wishes to be logged before finishing. 1124 * 1125 * <p>Transitions from this gate should {@link Log#log log} the process into a log file of their choice and 1126 * then proceed to the {@link #getStopGate "stop" gate}.</p> 1127 * 1128 * @override Sometimes As a default returns a gate with a transition that will log the process using the 1129 * process context's {@link ProcessContext#log log()} method.</p> 1130 */ 1131 public Gate getLogGate() { 1132 return new Gate() { 1133 private static final long serialVersionUID = 7675303739554233395L; 1134 1135 public Transition getNextTransition(SaleProcess p, User u) { 1136 return new Transition() { 1137 private static final long serialVersionUID = -933458497958394427L; 1138 1139 public Gate perform(SaleProcess p, User u) { 1140 try { 1141 p.log(p); 1142 } 1143 catch (java.io.IOException ioe) { 1144 throw new Error("Exception occurred while logging process: " + ioe); 1145 } 1146 1147 return p.getStopGate(); 1148 } 1149 }; 1150 } 1151 }; 1152 } 1153 1154 /** 1155 * Return the last gate that this process should be at. 1156 * 1157 * <p> Transitions from this gate must return <code>null</code> instead of a next gate.</p> 1158 * 1159 * @override Sometimes As a default just return <code>null</code>, indicating no further processing is to be 1160 * performed.</p> 1161 */ 1162 public Gate getStopGate() { 1163 return null; 1164 } 1165 1166 /** 1167 * A LogEntryFilter that will accept only such {@link LogEntry LogEntries} that stem from a process. 1168 */ 1169 public static final LogEntryFilter LOGENTRYFILTER_PROCESSES_ONLY = new LogEntryFilter() { 1170 public boolean accept(LogEntry le) { 1171 return (le instanceof ProcessLogEntry); 1172 } 1173 }; 1174 }