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 }