001: /*
002: * JBoss, Home of Professional Open Source
003: * Copyright 2005, JBoss Inc., and individual contributors as indicated
004: * by the @authors tag. See the copyright.txt in the distribution for a
005: * full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jbpm.taskmgmt.exe;
023:
024: import java.util.ArrayList;
025: import java.util.Date;
026: import java.util.HashSet;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Set;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.jbpm.JbpmException;
034: import org.jbpm.calendar.BusinessCalendar;
035: import org.jbpm.calendar.Duration;
036: import org.jbpm.context.exe.ContextInstance;
037: import org.jbpm.context.exe.VariableContainer;
038: import org.jbpm.context.exe.VariableInstance;
039: import org.jbpm.graph.def.Event;
040: import org.jbpm.graph.def.Node;
041: import org.jbpm.graph.def.Transition;
042: import org.jbpm.graph.exe.Comment;
043: import org.jbpm.graph.exe.ExecutionContext;
044: import org.jbpm.graph.exe.ProcessInstance;
045: import org.jbpm.graph.exe.Token;
046: import org.jbpm.graph.node.TaskNode;
047: import org.jbpm.security.SecurityHelper;
048: import org.jbpm.taskmgmt.def.Swimlane;
049: import org.jbpm.taskmgmt.def.Task;
050: import org.jbpm.taskmgmt.def.TaskController;
051: import org.jbpm.taskmgmt.log.TaskAssignLog;
052: import org.jbpm.taskmgmt.log.TaskEndLog;
053: import org.jbpm.util.Clock;
054: import org.jbpm.util.EqualsUtil;
055:
056: /**
057: * is one task instance that can be assigned to an actor (read: put in
058: * someones task list) and that can trigger the coninuation of execution
059: * of the token upon completion.
060: */
061: public class TaskInstance extends VariableContainer implements
062: Assignable {
063:
064: private static final long serialVersionUID = 1L;
065:
066: long id = 0;
067: int version = 0;
068: protected String name = null;
069: protected String description = null;
070: protected String actorId = null;
071: protected Date create = null;
072: protected Date start = null;
073: protected Date end = null;
074: protected Date dueDate = null;
075: protected int priority = Task.PRIORITY_NORMAL;
076: protected boolean isCancelled = false;
077: protected boolean isSuspended = false;
078: protected boolean isOpen = true;
079: protected boolean isSignalling = true;
080: protected boolean isBlocking = false;
081: protected Task task = null;
082: protected Token token = null;
083: protected SwimlaneInstance swimlaneInstance = null;
084: protected TaskMgmtInstance taskMgmtInstance = null;
085: protected ProcessInstance processInstance = null;
086: protected Set pooledActors = null;
087: protected List comments = null;
088:
089: protected String previousActorId = null; // not persisted. just extra information for listeners of the assign-event
090:
091: public TaskInstance() {
092: }
093:
094: public TaskInstance(String taskName) {
095: this .name = taskName;
096: }
097:
098: public TaskInstance(String taskName, String actorId) {
099: this .name = taskName;
100: this .actorId = actorId;
101: }
102:
103: public void setTask(Task task) {
104: this .name = task.getName();
105: this .description = task.getDescription();
106: this .task = task;
107: this .isBlocking = task.isBlocking();
108: this .priority = task.getPriority();
109: if (task.getTaskNode() != null) {
110: int signal = task.getTaskNode().getSignal();
111: this .isSignalling = ((signal == TaskNode.SIGNAL_FIRST)
112: || (signal == TaskNode.SIGNAL_LAST)
113: || (signal == TaskNode.SIGNAL_FIRST_WAIT) || (signal == TaskNode.SIGNAL_LAST_WAIT));
114: }
115: if (task.getDueDate() != null) {
116: BusinessCalendar businessCalendar = new BusinessCalendar();
117: this .dueDate = businessCalendar.add(Clock.getCurrentTime(),
118: new Duration(task.getDueDate()));
119: }
120: }
121:
122: void submitVariables() {
123: TaskController taskController = (task != null ? task
124: .getTaskController() : null);
125: // if there is a task controller,
126: if (taskController != null) {
127: // the task controller is responsible for copying variables back into the process
128: taskController.submitParameters(this );
129:
130: // if there is no task controller
131: } else if ((token != null)
132: && (token.getProcessInstance() != null)) {
133: // the default behaviour is that all task-local variables are flushed to the process
134: if (variableInstances != null) {
135: ContextInstance contextInstance = token
136: .getProcessInstance().getContextInstance();
137: Iterator iter = variableInstances.values().iterator();
138: while (iter.hasNext()) {
139: VariableInstance variableInstance = (VariableInstance) iter
140: .next();
141: log.debug("flushing variable '"
142: + variableInstance.getName()
143: + "' from task '" + name
144: + "' to process variables");
145: // This might be optimized, but this was the simplest way to make a clone of the variable instance.
146: contextInstance.setVariable(variableInstance
147: .getName(), variableInstance.getValue(),
148: token);
149: }
150: }
151: }
152: }
153:
154: void initializeVariables() {
155: TaskController taskController = (task != null ? task
156: .getTaskController() : null);
157: if (taskController != null) {
158: taskController.initializeVariables(this );
159: }
160: }
161:
162: public void create() {
163: create(null);
164: }
165:
166: public void create(ExecutionContext executionContext) {
167: if (create != null) {
168: throw new IllegalStateException("task instance '" + id
169: + "' was already created");
170: }
171: create = Clock.getCurrentTime();
172:
173: // if this task instance is associated with a task...
174: if ((task != null) && (executionContext != null)) {
175: // the TASK_CREATE event is fired
176: executionContext.setTaskInstance(this );
177: executionContext.setTask(task);
178: task.fireEvent(Event.EVENTTYPE_TASK_CREATE,
179: executionContext);
180: }
181:
182: // WARNING: The events create and assign are fired in the right order, but
183: // the logs are still not ordered properly.
184: // See also: TaskMgmtInstance.createTaskInstance
185: }
186:
187: public void assign(ExecutionContext executionContext) {
188: TaskMgmtInstance taskMgmtInstance = executionContext
189: .getTaskMgmtInstance();
190:
191: Swimlane swimlane = task.getSwimlane();
192: // if this task is in a swimlane
193: if (swimlane != null) {
194:
195: // if this is a task assignment for a start-state
196: if (isStartTaskInstance()) {
197: // initialize the swimlane
198: swimlaneInstance = new SwimlaneInstance(swimlane);
199: taskMgmtInstance.addSwimlaneInstance(swimlaneInstance);
200: // with the current authenticated actor
201: swimlaneInstance.setActorId(SecurityHelper
202: .getAuthenticatedActorId());
203:
204: } else {
205:
206: // lazy initialize the swimlane...
207: // get the swimlane instance (if there is any)
208: swimlaneInstance = taskMgmtInstance
209: .getInitializedSwimlaneInstance(
210: executionContext, swimlane);
211:
212: // copy the swimlaneInstance assignment into the taskInstance assignment
213: copySwimlaneInstanceAssignment(swimlaneInstance);
214: }
215:
216: } else { // this task is not in a swimlane
217: taskMgmtInstance.performAssignment(task
218: .getAssignmentDelegation(), task
219: .getActorIdExpression(), task
220: .getPooledActorsExpression(), this ,
221: executionContext);
222: }
223:
224: updatePooledActorsReferences(swimlaneInstance);
225: }
226:
227: public boolean isStartTaskInstance() {
228: boolean isStartTaskInstance = false;
229: if ((taskMgmtInstance != null)
230: && (taskMgmtInstance.getTaskMgmtDefinition() != null)) {
231: isStartTaskInstance = ((task != null) && (task
232: .equals(taskMgmtInstance.getTaskMgmtDefinition()
233: .getStartTask())));
234: }
235: return isStartTaskInstance;
236: }
237:
238: void updatePooledActorsReferences(SwimlaneInstance swimlaneInstance) {
239: if (pooledActors != null) {
240: Iterator iter = pooledActors.iterator();
241: while (iter.hasNext()) {
242: PooledActor pooledActor = (PooledActor) iter.next();
243: pooledActor.setSwimlaneInstance(swimlaneInstance);
244: pooledActor.addTaskInstance(this );
245: }
246: }
247: }
248:
249: /**
250: * copies the assignment (that includes both the swimlaneActorId and the set of pooledActors) of
251: * the given swimlane into this taskInstance.
252: */
253: public void copySwimlaneInstanceAssignment(
254: SwimlaneInstance swimlaneInstance) {
255: setSwimlaneInstance(swimlaneInstance);
256: setActorId(swimlaneInstance.getActorId());
257: setPooledActors(swimlaneInstance.getPooledActors());
258: }
259:
260: /**
261: * gets the pool of actors for this task instance. If this task has a simlaneInstance
262: * and no pooled actors, the pooled actors of the swimlane instance are returned.
263: */
264: public Set getPooledActors() {
265: if ((swimlaneInstance != null)
266: && ((pooledActors == null) || (pooledActors.isEmpty()))) {
267: return swimlaneInstance.getPooledActors();
268: }
269: return pooledActors;
270: }
271:
272: /**
273: * (re)assign this task to the given actor. If this task is related
274: * to a swimlane instance, that swimlane instance will be updated as well.
275: */
276: public void setActorId(String actorId) {
277: setActorId(actorId, true);
278: }
279:
280: /**
281: * (re)assign this task to the given actor.
282: * @param actorId is reference to the person that is assigned to this task.
283: * @param overwriteSwimlane specifies if the related swimlane
284: * should be overwritten with the given swimlaneActorId.
285: */
286: public void setActorId(String actorId, boolean overwriteSwimlane) {
287: // do the actual assignment
288: this .previousActorId = this .actorId;
289: this .actorId = actorId;
290: if ((swimlaneInstance != null) && (overwriteSwimlane)) {
291: log.debug("assigning task '" + name + "' to '" + actorId
292: + "'");
293: swimlaneInstance.setActorId(actorId);
294: }
295:
296: // fire the event
297: if ((task != null) && (token != null)) {
298: ExecutionContext executionContext = new ExecutionContext(
299: token);
300: executionContext.setTask(task);
301: executionContext.setTaskInstance(this );
302:
303: // WARNING: The events create and assign are fired in the right order, but
304: // the logs are still not ordered properly.
305: // See also: TaskMgmtInstance.createTaskInstance
306: task.fireEvent(Event.EVENTTYPE_TASK_ASSIGN,
307: executionContext);
308: }
309:
310: // add the log
311: if (token != null) {
312: // log this assignment
313: token.addLog(new TaskAssignLog(this , previousActorId,
314: actorId));
315: }
316: }
317:
318: /** takes a set of String's as the actorIds */
319: public void setPooledActors(String[] actorIds) {
320: this .pooledActors = PooledActor
321: .createPool(actorIds, null, this );
322: }
323:
324: /**
325: * can optionally be used to indicate that the actor is starting to
326: * work on this task instance.
327: */
328: public void start() {
329: if (start != null) {
330: throw new IllegalStateException("task instance '" + id
331: + "' is already started");
332: }
333:
334: start = Clock.getCurrentTime();
335: if ((task != null) && (token != null)) {
336: ExecutionContext executionContext = new ExecutionContext(
337: token);
338: executionContext.setTask(task);
339: executionContext.setTaskInstance(this );
340: task
341: .fireEvent(Event.EVENTTYPE_TASK_START,
342: executionContext);
343: }
344: }
345:
346: /**
347: * convenience method that combines a {@link #setActorId(String)} and
348: * a {@link #start()}.
349: */
350: public void start(String actorId) {
351: start(actorId, true);
352: }
353:
354: /**
355: * convenience method that combines a {@link #setActorId(String,boolean)} and
356: * a {@link #start()}.
357: */
358: public void start(String actorId, boolean overwriteSwimlane) {
359: setActorId(actorId, overwriteSwimlane);
360: start();
361: }
362:
363: /**
364: * overwrite start date
365: */
366: public void setStart(Date date) {
367: start = null;
368: }
369:
370: private void markAsCancelled() {
371: this .isCancelled = true;
372: this .isOpen = false;
373: }
374:
375: /**
376: * cancels this task.
377: * This task intance will be marked as cancelled and as ended. But cancellation
378: * doesn't influence singalling and continuation of process execution.
379: */
380: public void cancel() {
381: markAsCancelled();
382: end();
383: }
384:
385: /**
386: * cancels this task, takes the specified transition.
387: * This task intance will be marked as cancelled and as ended. But cancellation
388: * doesn't influence singalling and continuation of process execution.
389: */
390: public void cancel(Transition transition) {
391: markAsCancelled();
392: end(transition);
393: }
394:
395: /**
396: * cancels this task, takes the specified transition.
397: * This task intance will be marked as cancelled and as ended. But cancellation
398: * doesn't influence singalling and continuation of process execution.
399: */
400: public void cancel(String transitionName) {
401: markAsCancelled();
402: end(transitionName);
403: }
404:
405: /**
406: * marks this task as done. If this task is related to a task node
407: * this might trigger a signal on the token.
408: * @see #end(Transition)
409: */
410: public void end() {
411: end((Transition) null);
412: }
413:
414: /**
415: * marks this task as done and specifies the name of a transition
416: * leaving the task-node for the case that the completion of this
417: * task instances triggers a signal on the token.
418: * If this task leads to a signal on the token, the given transition
419: * name will be used in the signal.
420: * If this task completion does not trigger execution to move on,
421: * the transitionName is ignored.
422: */
423: public void end(String transitionName) {
424: Transition leavingTransition = null;
425:
426: if (task != null) {
427: Node node = task.getTaskNode();
428: if (node == null) {
429: node = (Node) task.getParent();
430: }
431:
432: if (node != null) {
433: leavingTransition = node
434: .getLeavingTransition(transitionName);
435: }
436: }
437: if (leavingTransition == null) {
438: throw new JbpmException(
439: "task node does not have leaving transition '"
440: + transitionName + "'");
441: }
442: end(leavingTransition);
443: }
444:
445: /**
446: * marks this task as done and specifies a transition
447: * leaving the task-node for the case that the completion of this
448: * task instances triggers a signal on the token.
449: * If this task leads to a signal on the token, the given transition
450: * name will be used in the signal.
451: * If this task completion does not trigger execution to move on,
452: * the transition is ignored.
453: */
454: public void end(Transition transition) {
455: if (this .end != null) {
456: throw new IllegalStateException("task instance '" + id
457: + "' is already ended");
458: }
459: if (this .isSuspended) {
460: throw new JbpmException("task instance '" + id
461: + "' is suspended");
462: }
463:
464: // mark the end of this task instance
465: this .end = Clock.getCurrentTime();
466: this .isOpen = false;
467:
468: // fire the task instance end event
469: if ((task != null) && (token != null)) {
470: ExecutionContext executionContext = new ExecutionContext(
471: token);
472: executionContext.setTask(task);
473: executionContext.setTaskInstance(this );
474: task.fireEvent(Event.EVENTTYPE_TASK_END, executionContext);
475: }
476:
477: // log this assignment
478: if (token != null) {
479: token.addLog(new TaskEndLog(this ));
480: }
481:
482: // submit the variables
483: submitVariables();
484:
485: // verify if the end of this task triggers continuation of execution
486: if (isSignalling) {
487: this .isSignalling = false;
488:
489: if (this .isStartTaskInstance() // ending start tasks always leads to a signal
490: || ((task != null) && (token != null)
491: && (task.getTaskNode() != null) && (task
492: .getTaskNode()
493: .completionTriggersSignal(this )))) {
494:
495: if (transition == null) {
496: log
497: .debug("completion of task '"
498: + task.getName()
499: + "' results in taking the default transition");
500: token.signal();
501: } else {
502: log.debug("completion of task '" + task.getName()
503: + "' results in taking transition '"
504: + transition + "'");
505: token.signal(transition);
506: }
507: }
508: }
509: }
510:
511: public boolean hasEnded() {
512: return (end != null);
513: }
514:
515: /**
516: * suspends a process execution.
517: */
518: public void suspend() {
519: if (!isOpen) {
520: throw new JbpmException(
521: "a task that is not open cannot be suspended: "
522: + toString());
523: }
524: isSuspended = true;
525: }
526:
527: /**
528: * resumes a process execution.
529: */
530: public void resume() {
531: if (!isOpen) {
532: throw new JbpmException(
533: "a task that is not open cannot be resumed: "
534: + toString());
535: }
536: isSuspended = false;
537: }
538:
539: // comments /////////////////////////////////////////////////////////////////
540:
541: public void addComment(String message) {
542: addComment(new Comment(message));
543: }
544:
545: public void addComment(Comment comment) {
546: if (comment != null) {
547: if (comments == null)
548: comments = new ArrayList();
549: comments.add(comment);
550: comment.setTaskInstance(this );
551: if (token != null) {
552: comment.setToken(token);
553: token.addComment(comment);
554: }
555: }
556: }
557:
558: public List getComments() {
559: return comments;
560: }
561:
562: // task form ////////////////////////////////////////////////////////////////
563:
564: public boolean isLast() {
565: return ((token != null) && (taskMgmtInstance != null) && (!taskMgmtInstance
566: .hasUnfinishedTasks(token)));
567: }
568:
569: /**
570: * is the list of transitions that can be used in the end method
571: * and it is null in case this is not the last task instance.
572: */
573: public List getAvailableTransitions() {
574: List transitions = null;
575: if ((!isLast()) && (token != null)) {
576: transitions = new ArrayList(token.getAvailableTransitions());
577: }
578: return transitions;
579: }
580:
581: // equals ///////////////////////////////////////////////////////////////////
582: // hack to support comparing hibernate proxies against the real objects
583: // since this always falls back to ==, we don't need to overwrite the hashcode
584: public boolean equals(Object o) {
585: return EqualsUtil.equals(this , o);
586: }
587:
588: public String toString() {
589: return "TaskInstance"
590: + (name != null ? "[" + name + "]" : Integer
591: .toHexString(System.identityHashCode(this )));
592: }
593:
594: // private //////////////////////////////////////////////////////////////////
595:
596: /** takes a set of {@link PooledActor}s */
597: public void setPooledActors(Set pooledActors) {
598: if (pooledActors != null) {
599: this .pooledActors = new HashSet(pooledActors);
600: Iterator iter = pooledActors.iterator();
601: while (iter.hasNext()) {
602: PooledActor pooledActor = (PooledActor) iter.next();
603: pooledActor.addTaskInstance(this );
604: }
605: } else {
606: pooledActors = null;
607: }
608: }
609:
610: // protected ////////////////////////////////////////////////////////////////
611:
612: protected VariableContainer getParentVariableContainer() {
613: ContextInstance contextInstance = getContextInstance();
614: return (contextInstance != null ? contextInstance
615: .getOrCreateTokenVariableMap(token) : null);
616: }
617:
618: // getters and setters //////////////////////////////////////////////////////
619:
620: public String getActorId() {
621: return actorId;
622: }
623:
624: public Date getDueDate() {
625: return dueDate;
626: }
627:
628: public void setDueDate(Date dueDate) {
629: this .dueDate = dueDate;
630: }
631:
632: public Date getEnd() {
633: return end;
634: }
635:
636: public void setEnd(Date end) {
637: this .end = end;
638: }
639:
640: public void setCreate(Date create) {
641: this .create = create;
642: }
643:
644: public long getId() {
645: return id;
646: }
647:
648: public void setId(long id) {
649: this .id = id;
650: }
651:
652: public Date getStart() {
653: return start;
654: }
655:
656: public TaskMgmtInstance getTaskMgmtInstance() {
657: return taskMgmtInstance;
658: }
659:
660: public void setTaskMgmtInstance(TaskMgmtInstance taskMgmtInstance) {
661: this .taskMgmtInstance = taskMgmtInstance;
662: }
663:
664: public Token getToken() {
665: return token;
666: }
667:
668: public void setToken(Token token) {
669: this .token = token;
670: }
671:
672: public void setSignalling(boolean isSignalling) {
673: this .isSignalling = isSignalling;
674: }
675:
676: public boolean isSignalling() {
677: return isSignalling;
678: }
679:
680: public boolean isCancelled() {
681: return isCancelled;
682: }
683:
684: public String getName() {
685: return name;
686: }
687:
688: public void setName(String name) {
689: this .name = name;
690: }
691:
692: public boolean isBlocking() {
693: return isBlocking;
694: }
695:
696: public void setBlocking(boolean isBlocking) {
697: this .isBlocking = isBlocking;
698: }
699:
700: public Date getCreate() {
701: return create;
702: }
703:
704: public Task getTask() {
705: return task;
706: }
707:
708: public SwimlaneInstance getSwimlaneInstance() {
709: return swimlaneInstance;
710: }
711:
712: public void setSwimlaneInstance(SwimlaneInstance swimlaneInstance) {
713: this .swimlaneInstance = swimlaneInstance;
714: }
715:
716: public String getPreviousActorId() {
717: return previousActorId;
718: }
719:
720: public int getPriority() {
721: return priority;
722: }
723:
724: public void setPriority(int priority) {
725: this .priority = priority;
726: }
727:
728: public boolean isOpen() {
729: return isOpen;
730: }
731:
732: public String getDescription() {
733: return description;
734: }
735:
736: public void setDescription(String description) {
737: this .description = description;
738: }
739:
740: public boolean isSuspended() {
741: return isSuspended;
742: }
743:
744: public ProcessInstance getProcessInstance() {
745: return processInstance;
746: }
747:
748: public void setProcessInstance(ProcessInstance processInstance) {
749: this .processInstance = processInstance;
750: }
751:
752: private static final Log log = LogFactory
753: .getLog(TaskInstance.class);
754: }
|