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    }