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 }