Source Code Cross Referenced for Workflow.java in  » Wiki-Engine » JSPWiki » com » ecyrd » jspwiki » workflow » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Wiki Engine » JSPWiki » com.ecyrd.jspwiki.workflow 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /* 
002:            JSPWiki - a JSP-based WikiWiki clone.
003:
004:            Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006:            This program is free software; you can redistribute it and/or modify
007:            it under the terms of the GNU Lesser General Public License as published by
008:            the Free Software Foundation; either version 2.1 of the License, or
009:            (at your option) any later version.
010:
011:            This program is distributed in the hope that it will be useful,
012:            but WITHOUT ANY WARRANTY; without even the implied warranty of
013:            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014:            GNU Lesser General Public License for more details.
015:
016:            You should have received a copy of the GNU Lesser General Public License
017:            along with this program; if not, write to the Free Software
018:            Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
019:         */
020:        package com.ecyrd.jspwiki.workflow;
021:
022:        import java.security.Principal;
023:        import java.util.*;
024:
025:        import com.ecyrd.jspwiki.WikiException;
026:        import com.ecyrd.jspwiki.event.WikiEventListener;
027:        import com.ecyrd.jspwiki.event.WikiEventManager;
028:        import com.ecyrd.jspwiki.event.WorkflowEvent;
029:
030:        /**
031:         * <p>
032:         * Sequence of {@link Step} objects linked together. Workflows are always
033:         * initialized with a message key that denotes the name of the Workflow, and a
034:         * Principal that represents its owner.
035:         * </p>
036:         * <h2>Workflow lifecycle</h2>
037:         * A Workflow's state (obtained by {@link #getCurrentState()}) will be one of the
038:         * following:
039:         * </p>
040:         * <ul>
041:         * <li><strong>{@link #CREATED}</strong>: after the Workflow has been
042:         * instantiated, but before it has been started using the {@link #start()}
043:         * method.</li>
044:         * <li><strong>{@link #RUNNING}</strong>: after the Workflow has been started
045:         * using the {@link #start()} method, but before it has finished processing all
046:         * Steps. Note that a Workflow can only be started once; attempting to start it
047:         * again results in an IllegalStateException. Callers can place the Workflow
048:         * into the WAITING state by calling {@link #waitstate()}.</li>
049:         * <li><strong>{@link #WAITING}</strong>: when the Workflow has temporarily
050:         * paused, for example because of a pending Decision. Once the responsible actor
051:         * decides what to do, the caller can change the Workflow back to the RUNNING
052:         * state by calling the {@link #restart()} method (this is done automatically by
053:         * the Decision class, for instance, when the {@link Decision#decide(Outcome)}
054:         * method is invoked)</li>
055:         * <li><strong>{@link #COMPLETED}</strong>: after the Workflow has finished
056:         * processing all Steps, without errors.</li>
057:         * <li><strong>{@link #ABORTED}</strong>: if a Step has elected to abort the
058:         * Workflow.</li>
059:         * </ul>
060:         * <h2>Steps and processing algorithm</h2>
061:         * <p>
062:         * Workflow Step objects can be of type {@link Decision}, {@link Task} or other
063:         * Step subclasses. Decisions require user input, while Tasks do not. See the
064:         * {@link Step} class for more details.
065:         * </p>
066:         * <p>
067:         * After instantiating a new Workflow (but before telling it to {@link #start()}),
068:         * calling classes should specify the first Step by executing the
069:         * {@link #setFirstStep(Step)} method. Additional Steps can be chained by
070:         * invoking the first step's {@link Step#addSuccessor(Outcome, Step)} method.
071:         * </p>
072:         * <p>
073:         * When a Workflow's <code>start</code> method is invoked, the Workflow
074:         * retrieves the first Step and processes it. This Step, and subsequent ones,
075:         * are processed as follows:
076:         * </p>
077:         * <ul>
078:         * <li>The Step's {@link Step#start()} method executes, which sets the start
079:         * time.</li>
080:         * <li>The Step's {@link Step#execute()} method is called to begin processing,
081:         * which will return an Outcome to indicate completion, continuation or errors:</li>
082:         * <ul>
083:         * <li>{@link Outcome#STEP_COMPLETE} indicates that the execution method ran
084:         * without errors, and that the Step should be considered "completed."</li>
085:         * <li>{@link Outcome#STEP_CONTINUE} indicates that the execution method ran
086:         * without errors, but that the Step is not "complete" and should be put into
087:         * the WAITING state.</li>
088:         * <li>{@link Outcome#STEP_ABORT} indicates that the execution method
089:         * encountered errors, and should abort the Step <em>and</em> the Workflow as
090:         * a whole. When this happens, the Workflow will set the current Step's Outcome
091:         * to {@link Outcome#STEP_ABORT} and invoke the Workflow's {@link #abort()}
092:         * method. The Step's processing errors, if any, can be retrieved by
093:         * {@link Step#getErrors()}.</li>
094:         * </ul>
095:         * <li>The Outcome of the <code>execute</code> method also affects what
096:         * happens next. Depending on the result (and assuming the Step did not abort),
097:         * the Workflow will either move on to the next Step or put the Workflow into
098:         * the {@link Workflow#WAITING} state:</li>
099:         * <ul>
100:         * <li>If the Outcome denoted "completion" (<em>i.e.</em>, its
101:         * {@link Step#isCompleted()} method returns <code>true</code>) then the Step
102:         * is considered complete; the Workflow looks up the next Step by calling the
103:         * current Step's {@link Step#getSuccessor(Outcome)} method. If
104:         * <code>successor()</code> returns a non-<code>null</code> Step, the
105:         * return value is marked as the current Step and added to the Workflow's Step
106:         * history. If <code>successor()</code> returns <code>null</code>, then the
107:         * Workflow has no more Steps and it enters the {@link #COMPLETED} state.</li>
108:         * <li>If the Outcome did not denote "completion" (<em>i.e.</em>, its
109:         * {@link Step#isCompleted()} method returns <code>false</code>), then the
110:         * Step still has further work to do. The Workflow enters the {@link #WAITING}
111:         * state and stops further processing until a caller restarts it.</li>
112:         * </ul>
113:         * </ul>
114:         * </p>
115:         * <p>
116:         * The currently executing Step can be obtained by {@link #getCurrentStep()}. The
117:         * actor for the current Step is returned by {@link #getCurrentActor()}.
118:         * </p>
119:         * <p>
120:         * To provide flexibility for specific implementations, the Workflow class
121:         * provides two additional features that enable Workflow participants (<em>i.e.</em>,
122:         * Workflow subclasses and Step/Task/Decision subclasses) to share context and
123:         * state information. These two features are <em>named attributes</em> and
124:         * <em>message arguments</em>:
125:         * </p>
126:         * <ul>
127:         * <li><strong>Named attributes</strong> are simple key-value pairs that
128:         * Workflow participants can get or set. Keys are Strings; values can be any
129:         * Object. Named attributes are set with {@link #setAttribute(String, Object)}
130:         * and retrieved with {@link #getAttribute(String)}.</li>
131:         * <li><strong>Message arguments</strong> are used in combination with
132:         * JSPWiki's {@link com.ecyrd.jspwiki.i18n.InternationalizationManager} to
133:         * create language-independent user interface messages. The message argument
134:         * array is retrieved via {@link #getMessageArguments()}; the first two array
135:         * elements will always be these: a String representing work flow owner's name,
136:         * and a String representing the current actor's name. Workflow participants
137:         * can add to this array by invoking {@link #addMessageArgument(Object)}.</li>
138:         * </ul>
139:         * <h2>Example</h2>
140:         * <p>
141:         * Workflow Steps can be very powerful when linked together. JSPWiki provides
142:         * two abstract subclasses classes that you can use to build your own Workflows:
143:         * Tasks and Decisions. As noted, Tasks are Steps that execute without user
144:         * intervention, while Decisions require actors (<em>aka</em> Principals) to
145:         * take action. Decisions and Tasks can be mixed freely to produce some highly
146:         * elaborate branching structures.
147:         * </p>
148:         * <p>
149:         * Here is a simple case. For example, suppose you would like to create a
150:         * Workflow that (a) executes a initialization Task, (b) pauses to obtain an
151:         * approval Decision from a user in the Admin group, and if approved, (c)
152:         * executes a "finish" Task. Here's sample code that illustrates how to do it:
153:         * </p>
154:         *
155:         * <pre>
156:         *    // Create workflow; owner is current user
157:         * 1  Workflow workflow = new Workflow(&quot;workflow.myworkflow&quot;, context.getCurrentUser());
158:         *
159:         *    // Create custom initialization task
160:         * 2  Step initTask = new InitTask(this);
161:         *
162:         *    // Create finish task
163:         * 3  Step finishTask = new FinishTask(this);
164:         *
165:         *    // Create an intermediate decision step
166:         * 4  Principal actor = new GroupPrincipal(&quot;Admin&quot;);
167:         * 5  Step decision = new SimpleDecision(this, &quot;decision.AdminDecision&quot;, actor);
168:         *
169:         *    // Hook the steps together
170:         * 6  initTask.addSuccessor(Outcome.STEP_COMPLETE, decision);
171:         * 7  decision.addSuccessor(Outcome.DECISION_APPROVE, finishTask);
172:         *
173:         *    // Set workflow's first step
174:         * 8  workflow.setFirstStep(initTask);
175:         * </pre>
176:         *
177:         * <p>
178:         * Some comments on the source code:
179:         * </p>
180:         * <ul>
181:         * <li>Line 1 instantiates the workflow with a sample message key and
182:         * designated owner Principal, in this case the current wiki user</li>
183:         * <li>Lines 2 and 3 instantiate the custom Task subclasses, which contain the
184:         * business logic</li>
185:         * <li>Line 4 creates the relevant GroupPrincipal for the <code>Admin</code>
186:         * group, who will be the actor in the Decision step</li>
187:         * <li>Line 5 creates the Decision step, passing the Workflow, sample message
188:         * key, and actor in the constructor</li>
189:         * <li>Line 6 specifies that if the InitTask's Outcome signifies "normal
190:         * completion" (STEP_COMPLETE), the SimpleDecision step should be invoked next</li>
191:         * <li>Line 7 specifies that if the actor (anyone possessing the
192:         * <code>Admin</code> GroupPrincipal) selects DECISION_APPROVE, the FinishTask
193:         * step should be invoked</li>
194:         * <li>Line 8 adds the InitTask (and all of its successor Steps, nicely wired
195:         * together) to the workflow</li>
196:         * </ul>
197:         *
198:         * @author Andrew Jaquith
199:         */
200:        public class Workflow {
201:            /** Time value: the start or end time has not been set. */
202:            public static final Date TIME_NOT_SET = new Date(0);
203:
204:            /** ID value: the workflow ID has not been set. */
205:            public static final int ID_NOT_SET = 0;
206:
207:            /** State value: Workflow completed all Steps without errors. */
208:            public static final int COMPLETED = 50;
209:
210:            /** State value: Workflow aborted before completion. */
211:            public static final int ABORTED = 40;
212:
213:            /**
214:             * State value: Workflow paused, typically because a Step returned an
215:             * Outcome that doesn't signify "completion."
216:             */
217:            public static final int WAITING = 30;
218:
219:            /** State value: Workflow started, and is running. */
220:            public static final int RUNNING = -1;
221:
222:            /** State value: Workflow instantiated, but not started. */
223:            public static final int CREATED = -2;
224:
225:            /** Lazily-initialized attribute map. */
226:            private Map m_attributes;
227:
228:            /** The initial Step for this Workflow. */
229:            private Step m_firstStep;
230:
231:            /** Flag indicating whether the Workflow has started yet. */
232:            private boolean m_started;
233:
234:            private final LinkedList m_history;
235:
236:            private int m_id;
237:
238:            private final String m_key;
239:
240:            private final Principal m_owner;
241:
242:            private final List m_messageArgs;
243:
244:            private int m_state;
245:
246:            private Step m_currentStep;
247:
248:            private WorkflowManager m_manager;
249:
250:            /**
251:             * Constructs a new Workflow object with a supplied message key, owner
252:             * Principal, and undefined unique identifier {@link #ID_NOT_SET}. Once
253:             * instantiated the Workflow is considered to be in the {@link #CREATED}
254:             * state; a caller must explicitly invoke the {@link #start()} method to
255:             * begin processing.
256:             *
257:             * @param messageKey
258:             *            the message key used to construct a localized workflow name,
259:             *            such as <code>workflow.saveWikiPage</code>
260:             * @param owner
261:             *            the Principal who owns the Workflow. Typically, this is the
262:             *            user who created and submitted it
263:             */
264:            public Workflow(String messageKey, Principal owner) {
265:                super ();
266:                m_attributes = null;
267:                m_currentStep = null;
268:                m_history = new LinkedList();
269:                m_id = ID_NOT_SET;
270:                m_key = messageKey;
271:                m_manager = null;
272:                m_messageArgs = new ArrayList();
273:                m_owner = owner;
274:                m_started = false;
275:                m_state = CREATED;
276:            }
277:
278:            /**
279:             * Aborts the Workflow by setting the current Step's Outcome to
280:             * {@link Outcome#STEP_ABORT}, and the Workflow's overall state to
281:             * {@link #ABORTED}. It also appends the aborted Step into the workflow
282:             * history, and sets the current step to <code>null</code>. If the Step
283:             * is a Decision, it is removed from the DecisionQueue. This method
284:             * can be called at any point in the lifecycle prior to completion, but it
285:             * cannot be called twice. It finishes by calling the {@link #cleanup()}
286:             * method to flush retained objects. If the Workflow had been previously
287:             * aborted, this method throws an IllegalStateException.
288:             */
289:            public final synchronized void abort() {
290:                // Check corner cases: previous abort or completion
291:                if (m_state == ABORTED) {
292:                    throw new IllegalStateException(
293:                            "The workflow has already been aborted.");
294:                }
295:                if (m_state == COMPLETED) {
296:                    throw new IllegalStateException(
297:                            "The workflow has already completed.");
298:                }
299:
300:                if (m_currentStep != null) {
301:                    if (m_manager != null && m_currentStep instanceof  Decision) {
302:                        Decision d = (Decision) m_currentStep;
303:                        m_manager.getDecisionQueue().remove(d);
304:                    }
305:                    m_currentStep.setOutcome(Outcome.STEP_ABORT);
306:                    m_history.addLast(m_currentStep);
307:                }
308:                m_state = ABORTED;
309:                fireEvent(WorkflowEvent.ABORTED);
310:                cleanup();
311:            }
312:
313:            /**
314:             * Appends a message argument object to the array returned by
315:             * {@link #getMessageArguments()}. The object <em>must</em> be an type
316:             * used by the {@link java.text.MessageFormat}: String, Date, or Number
317:             * (BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short).
318:             * If the object is not of type String, Number or Date, this method throws
319:             * an IllegalArgumentException.
320:             * @param obj the object to add
321:             */
322:            public final void addMessageArgument(Object obj) {
323:                if (obj instanceof  String || obj instanceof  Date
324:                        || obj instanceof  Number) {
325:                    m_messageArgs.add(obj);
326:                    return;
327:                }
328:                throw new IllegalArgumentException(
329:                        "Message arguments must be of type String, Date or Number.");
330:            }
331:
332:            /**
333:             * Returns the actor Principal responsible for the current Step. If there is
334:             * no current Step, this method returns <code>null</code>.
335:             *
336:             * @return the current actor
337:             */
338:            public final synchronized Principal getCurrentActor() {
339:                if (m_currentStep == null) {
340:                    return null;
341:                }
342:                return m_currentStep.getActor();
343:            }
344:
345:            /**
346:             * Returns the workflow state: {@link #CREATED}, {@link #RUNNING},
347:             * {@link #WAITING}, {@link #COMPLETED} or {@link #ABORTED}.
348:             *
349:             * @return the workflow state
350:             */
351:            public final int getCurrentState() {
352:                return m_state;
353:            }
354:
355:            /**
356:             * Returns the current Step, or <code>null</code> if the workflow has not
357:             * started or already completed.
358:             *
359:             * @return the current step
360:             */
361:            public final Step getCurrentStep() {
362:                return m_currentStep;
363:            }
364:
365:            /**
366:             * Retrieves a named Object associated with this Workflow. If the Workflow
367:             * has completed or aborted, this method always returns <code>null</code>.
368:             *
369:             * @param attr
370:             *            the name of the attribute
371:             * @return the value
372:             */
373:            public final synchronized Object getAttribute(String attr) {
374:                if (m_attributes == null) {
375:                    return null;
376:                }
377:                return m_attributes.get(attr);
378:            }
379:
380:            /**
381:             * The end time for this Workflow, expressed as a system time number. This
382:             * value is equal to the end-time value returned by the final Step's
383:             * {@link Step#getEndTime()} method, if the workflow has completed.
384:             * Otherwise, this method returns {@link #TIME_NOT_SET}.
385:             *
386:             * @return the end time
387:             */
388:            public final Date getEndTime() {
389:                if (isCompleted()) {
390:                    Step last = (Step) m_history.getLast();
391:                    if (last != null) {
392:                        return last.getEndTime();
393:                    }
394:                }
395:                return TIME_NOT_SET;
396:            }
397:
398:            /**
399:             * Returns the unique identifier for this Workflow. If not set, this method
400:             * returns ID_NOT_SET ({@value #ID_NOT_SET}).
401:             *
402:             * @return the unique identifier
403:             */
404:            public final synchronized int getId() {
405:                return m_id;
406:            }
407:
408:            /**
409:             * <p>
410:             * Returns an array of message arguments, used by
411:             * {@link java.text.MessageFormat} to create localized messages. The first
412:             * two array elements will always be these:
413:             * </p>
414:             * <ul>
415:             * <li>String representing the name of the workflow owner (<em>i.e.</em>,{@link #getOwner()})</li>
416:             * <li>String representing the name of the current actor (<em>i.e.</em>,{@link #getCurrentActor()}).
417:             * If the current step is <code>null</code> because the workflow hasn't started or has already
418:             * finished, the value of this argument will be a dash character (<code>-</code>)</li>
419:             * </ul>
420:             * <p>
421:             * Workflow and Step subclasses are free to append items to this collection
422:             * with {@link #addMessageArgument(Object)}.
423:             * </p>
424:             *
425:             * @return the array of message arguments
426:             */
427:            public final Object[] getMessageArguments() {
428:                List args = new ArrayList();
429:                args.add(m_owner.getName());
430:                Principal actor = getCurrentActor();
431:                args.add(actor == null ? "-" : actor.getName());
432:                args.addAll(m_messageArgs);
433:                return args.toArray(new Object[args.size()]);
434:            }
435:
436:            /**
437:             * Returns an i18n message key for the name of this workflow; for example,
438:             * <code>workflow.saveWikiPage</code>.
439:             *
440:             * @return the name
441:             */
442:            public final String getMessageKey() {
443:                return m_key;
444:            }
445:
446:            /**
447:             * The owner Principal on whose behalf this Workflow is being executed; that
448:             * is, the user who created the workflow.
449:             *
450:             * @return the name of the Principal who owns this workflow
451:             */
452:            public final Principal getOwner() {
453:                return m_owner;
454:            }
455:
456:            /**
457:             * The start time for this Workflow, expressed as a system time number. This
458:             * value is equal to the start-time value returned by the first Step's
459:             * {@link Step#getStartTime()} method, if the workflow has started already.
460:             * Otherwise, this method returns {@link #TIME_NOT_SET}.
461:             *
462:             * @return the start time
463:             */
464:            public final Date getStartTime() {
465:                return isStarted() ? m_firstStep.getStartTime() : TIME_NOT_SET;
466:            }
467:
468:            /**
469:             * Returns the WorkflowManager that contains this Workflow.
470:             *
471:             * @return the workflow manager
472:             */
473:            public final synchronized WorkflowManager getWorkflowManager() {
474:                return m_manager;
475:            }
476:
477:            /**
478:             * Returns a Step history for this Workflow as a List, chronologically, from the
479:             * first Step to the currently executing one. The first step is the first
480:             * item in the array. If the Workflow has not started, this method returns a
481:             * zero-length array.
482:             *
483:             * @return an array of Steps representing those that have executed, or are
484:             *         currently executing
485:             */
486:            public final List getHistory() {
487:                return Collections.unmodifiableList(m_history);
488:            }
489:
490:            /**
491:             * Returns <code>true</code> if the workflow had been previously aborted.
492:             *
493:             * @return the result
494:             */
495:            public final boolean isAborted() {
496:                return m_state == ABORTED;
497:            }
498:
499:            /**
500:             * Determines whether this Workflow is completed; that is, if it has no
501:             * additional Steps to perform. If the last Step in the workflow is
502:             * finished, this method will return <code>true</code>.
503:             *
504:             * @return <code>true</code> if the workflow has been started but has no
505:             *         more steps to perform; <code>false</code> if not.
506:             */
507:            public final synchronized boolean isCompleted() {
508:                // If current step is null, then we're done
509:                return m_started && m_state == COMPLETED;
510:            }
511:
512:            /**
513:             * Determines whether this Workflow has started; that is, its
514:             * {@link #start()} method has been executed.
515:             *
516:             * @return <code>true</code> if the workflow has been started;
517:             *         <code>false</code> if not.
518:             */
519:            public final boolean isStarted() {
520:                return m_started;
521:            }
522:
523:            /**
524:             * Convenience method that returns the predecessor of the current Step. This
525:             * method simply examines the Workflow history and returns the
526:             * second-to-last Step.
527:             *
528:             * @return the predecessor, or <code>null</code> if the first Step is
529:             *         currently executing
530:             */
531:            public final Step getPreviousStep() {
532:                return previousStep(m_currentStep);
533:            }
534:
535:            /**
536:             * Restarts the Workflow from the {@link #WAITING} state and puts it into
537:             * the {@link #RUNNING} state again. If the Workflow had not previously been
538:             * paused, this method throws an IllegalStateException. If any of the
539:             * Steps in this Workflow throw a WikiException, the Workflow will abort
540:             * and propagate the exception to callers.
541:             * @throws WikiException if the current task's {@link Task#execute()} method
542:             * throws an exception
543:             */
544:            public final synchronized void restart() throws WikiException {
545:                if (m_state != WAITING) {
546:                    throw new IllegalStateException(
547:                            "Workflow is not paused; cannot restart.");
548:                }
549:                m_state = RUNNING;
550:                fireEvent(WorkflowEvent.RUNNING);
551:
552:                // Process current step
553:                try {
554:                    processCurrentStep();
555:                } catch (WikiException e) {
556:                    abort();
557:                    throw e;
558:                }
559:            }
560:
561:            /**
562:             * Temporarily associates an Object with this Workflow, as a named attribute, for the
563:             * duration of workflow execution. The passed Object can be anything required by
564:             * an executing Step. Note that when the workflow completes or aborts, all
565:             * attributes will be cleared.
566:             *
567:             * @param attr
568:             *            the attribute name
569:             * @param obj
570:             *            the value
571:             */
572:            public final synchronized void setAttribute(String attr, Object obj) {
573:                if (m_attributes == null) {
574:                    m_attributes = new HashMap();
575:                }
576:                m_attributes.put(attr, obj);
577:            }
578:
579:            /**
580:             * Sets the first Step for this Workflow, which will be executed immediately
581:             * after the {@link #start()} method executes. Note than the Step is not
582:             * marked as the "current" step or added to the Workflow history until the
583:             * {@link #start()} method is called.
584:             *
585:             * @param step
586:             *            the first step for the workflow
587:             */
588:            public final synchronized void setFirstStep(Step step) {
589:                m_firstStep = step;
590:            }
591:
592:            /**
593:             * Sets the unique identifier for this Workflow.
594:             *
595:             * @param id
596:             *            the unique identifier
597:             */
598:            public final synchronized void setId(int id) {
599:                this .m_id = id;
600:            }
601:
602:            /**
603:             * Sets the WorkflowManager that contains this Workflow.
604:             *
605:             * @param manager
606:             *            the workflow manager
607:             */
608:            public final synchronized void setWorkflowManager(
609:                    WorkflowManager manager) {
610:                m_manager = manager;
611:                addWikiEventListener(manager);
612:            }
613:
614:            /**
615:             * Starts the Workflow and sets the state to {@link #RUNNING}. If the
616:             * Workflow has already been started (or previously aborted), this method
617:             * returns an {@linkplain IllegalStateException}. If any of the
618:             * Steps in this Workflow throw a WikiException, the Workflow will abort
619:             * and propagate the exception to callers.
620:             * @throws WikiException if the current Step's {@link Step#start()}
621:             * method throws an exception of any kind
622:             */
623:            public final synchronized void start() throws WikiException {
624:                if (m_state == ABORTED) {
625:                    throw new IllegalStateException(
626:                            "Workflow cannot be started; it has already been aborted.");
627:                }
628:                if (m_started) {
629:                    throw new IllegalStateException(
630:                            "Workflow has already started.");
631:                }
632:                m_started = true;
633:                m_state = RUNNING;
634:                fireEvent(WorkflowEvent.RUNNING);
635:
636:                // Mark the first step as the current one & add to history
637:                m_currentStep = m_firstStep;
638:                m_history.add(m_currentStep);
639:
640:                // Process current step
641:                try {
642:                    processCurrentStep();
643:                } catch (WikiException e) {
644:                    abort();
645:                    throw e;
646:                }
647:            }
648:
649:            /**
650:             * Sets the Workflow in the {@link #WAITING} state. If the Workflow is not
651:             * running or has already been paused, this method throws an
652:             * IllegalStateException. Once paused, the Workflow can be un-paused by
653:             * executing the {@link #restart()} method.
654:             */
655:            public final synchronized void waitstate() {
656:                if (m_state != RUNNING) {
657:                    throw new IllegalStateException(
658:                            "Workflow is not running; cannot pause.");
659:                }
660:                m_state = WAITING;
661:                fireEvent(WorkflowEvent.WAITING);
662:            }
663:
664:            /**
665:             * Clears the attribute map and sets the current step field to
666:             * <code>null</code>.
667:             */
668:            protected void cleanup() {
669:                m_currentStep = null;
670:                m_attributes = null;
671:            }
672:
673:            /**
674:             * Protected helper method that changes the Workflow's state to
675:             * {@link #COMPLETED} and sets the current Step to <code>null</code>. It
676:             * calls the {@link #cleanup()} method to flush retained objects.
677:             * This method will no-op if it has previously been called.
678:             */
679:            protected final synchronized void complete() {
680:                if (!isCompleted()) {
681:                    m_state = COMPLETED;
682:                    fireEvent(WorkflowEvent.COMPLETED);
683:                    cleanup();
684:                }
685:            }
686:
687:            /**
688:             * Protected method that returns the predecessor for a supplied Step.
689:             *
690:             * @param step
691:             *            the Step for which the predecessor is requested
692:             * @return its predecessor, or <code>null</code> if the first Step was
693:             *         supplied.
694:             */
695:            protected final Step previousStep(Step step) {
696:                int index = m_history.indexOf(step);
697:                return index < 1 ? null : (Step) m_history.get(index - 1);
698:            }
699:
700:            /**
701:             * Protected method that processes the current Step by calling
702:             * {@link Step#execute()}. If the <code>execute</code> throws an
703:             * exception, this method will propagate the exception immediately
704:             * to callers without aborting.
705:             * @throws WikiException if the current Step's {@link Step#start()}
706:             * method throws an exception of any kind
707:             */
708:            protected final void processCurrentStep() throws WikiException {
709:                while (m_currentStep != null) {
710:
711:                    // Start and execute the current step
712:                    if (!m_currentStep.isStarted()) {
713:                        m_currentStep.start();
714:                    }
715:                    try {
716:                        Outcome result = m_currentStep.execute();
717:                        if (Outcome.STEP_ABORT.equals(result)) {
718:                            abort();
719:                            break;
720:                        }
721:
722:                        if (!m_currentStep.isCompleted()) {
723:                            m_currentStep.setOutcome(result);
724:                        }
725:                    } catch (WikiException e) {
726:                        throw e;
727:                    }
728:
729:                    // Get the execution Outcome; if not complete, pause workflow and
730:                    // exit
731:                    Outcome outcome = m_currentStep.getOutcome();
732:                    if (!outcome.isCompletion()) {
733:                        waitstate();
734:                        break;
735:                    }
736:
737:                    // Get the next Step; if null, we're done
738:                    Step nextStep = m_currentStep.getSuccessor(outcome);
739:                    if (nextStep == null) {
740:                        complete();
741:                        break;
742:                    }
743:
744:                    // Add the next step to Workflow history, and mark as current
745:                    m_history.add(nextStep);
746:                    m_currentStep = nextStep;
747:                }
748:
749:            }
750:
751:            // events processing .......................................................
752:
753:            /**
754:             * Registers a WikiEventListener with this instance. This is a convenience
755:             * method.
756:             *
757:             * @param listener
758:             *            the event listener
759:             */
760:            public final synchronized void addWikiEventListener(
761:                    WikiEventListener listener) {
762:                WikiEventManager.addWikiEventListener(this , listener);
763:            }
764:
765:            /**
766:             * Un-registers a WikiEventListener with this instance. This is a
767:             * convenience method.
768:             *
769:             * @param listener
770:             *            the event listener
771:             */
772:            public final synchronized void removeWikiEventListener(
773:                    WikiEventListener listener) {
774:                WikiEventManager.removeWikiEventListener(this , listener);
775:            }
776:
777:            /**
778:             * Fires a WorkflowEvent of the provided type to all registered listeners.
779:             *
780:             * @see com.ecyrd.jspwiki.event.WorkflowEvent
781:             * @param type
782:             *            the event type to be fired
783:             */
784:            protected final void fireEvent(int type) {
785:                if (WikiEventManager.isListening(this )) {
786:                    WikiEventManager.fireEvent(this , new WorkflowEvent(this,
787:                            type));
788:                }
789:            }
790:
791:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.