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 &quot;error&quot; 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 &quot;error&quot; 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 &quot;error&quot; 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 &quot;%o&quot; 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         * &quot;quit&quot; gate.
871         *
872         * <p><code>quit()</code> first calls <code>suspend()</code> then sets the current
873         * gate to be the &quot;quit&quot; 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 &quot;stop&quot; gate.</p>
1020         *
1021         * @override Sometimes As a default, returns the &quot;rollback&quot; 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 &quot;rollback&quot; 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 &quot;rollback&quot; 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 &quot;rollback&quot; 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 &quot;log&quot; 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 &quot;log&quot; 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 &quot;stop&quot; 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    }