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.graph.exe;
023:
024: import java.io.Serializable;
025: import java.util.ArrayList;
026: import java.util.Date;
027: import java.util.HashMap;
028: import java.util.HashSet;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.Set;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.jbpm.JbpmContext;
037: import org.jbpm.JbpmException;
038: import org.jbpm.db.JobSession;
039: import org.jbpm.graph.def.Event;
040: import org.jbpm.graph.def.Node;
041: import org.jbpm.graph.def.ProcessDefinition;
042: import org.jbpm.graph.def.Transition;
043: import org.jbpm.graph.log.SignalLog;
044: import org.jbpm.graph.log.TokenCreateLog;
045: import org.jbpm.graph.log.TokenEndLog;
046: import org.jbpm.jpdl.el.impl.JbpmExpressionEvaluator;
047: import org.jbpm.logging.exe.LoggingInstance;
048: import org.jbpm.logging.log.CompositeLog;
049: import org.jbpm.logging.log.ProcessLog;
050: import org.jbpm.svc.Services;
051: import org.jbpm.taskmgmt.exe.TaskMgmtInstance;
052: import org.jbpm.util.Clock;
053: import org.jbpm.util.EqualsUtil;
054:
055: /**
056: * represents one path of execution and maintains a pointer to a node
057: * in the {@link org.jbpm.graph.def.ProcessDefinition}. Most common
058: * way to get a hold of the token objects is with {@link ProcessInstance#getRootToken()}
059: * or {@link org.jbpm.graph.exe.ProcessInstance#findToken(String)}.
060: */
061: public class Token implements Serializable {
062:
063: private static final long serialVersionUID = 1L;
064:
065: long id = 0;
066: int version = 0;
067: protected String name = null;
068: protected Date start = null;
069: protected Date end = null;
070: protected Node node = null;
071: protected Date nodeEnter = null;
072: protected ProcessInstance processInstance = null;
073: protected Token parent = null;
074: protected Map children = null;
075: protected List comments = null;
076: protected ProcessInstance subProcessInstance = null;
077: protected int nextLogIndex = 0;
078: boolean isAbleToReactivateParent = true;
079: boolean isTerminationImplicit = false;
080: boolean isSuspended = false;
081: String lock = null;
082:
083: // constructors
084: /////////////////////////////////////////////////////////////////////////////
085:
086: public Token() {
087: }
088:
089: /**
090: * creates a root token.
091: */
092: public Token(ProcessInstance processInstance) {
093: this .start = Clock.getCurrentTime();
094: this .processInstance = processInstance;
095: this .node = processInstance.getProcessDefinition()
096: .getStartState();
097: this .isTerminationImplicit = processInstance
098: .getProcessDefinition().isTerminationImplicit();
099:
100: // optimization: assigning an id is not necessary since the process instance will be saved shortly.
101: // Services.assignId(this);
102: }
103:
104: /**
105: * creates a child token.
106: */
107: public Token(Token parent, String name) {
108: this .start = Clock.getCurrentTime();
109: this .processInstance = parent.getProcessInstance();
110: this .name = name;
111: this .node = parent.getNode();
112: this .parent = parent;
113: parent.addChild(this );
114: this .isTerminationImplicit = parent.isTerminationImplicit();
115: parent.addLog(new TokenCreateLog(this ));
116:
117: // assign an id to this token before events get fired
118: Services.assignId(this );
119: }
120:
121: // operations
122: /////////////////////////////////////////////////////////////////////////////
123:
124: void addChild(Token token) {
125: if (children == null) {
126: children = new HashMap();
127: }
128: children.put(token.getName(), token);
129: }
130:
131: /**
132: * provides a signal to the token. this method activates this token and leaves
133: * the current state over the default transition.
134: */
135: public void signal() {
136: if (node == null) {
137: throw new JbpmException(
138: "token '"
139: + this
140: + "' can't be signalled cause it is currently not positioned in a node");
141: }
142: if (node.getDefaultLeavingTransition() == null) {
143: throw new JbpmException("couldn't signal token '" + this
144: + "' : node '" + node
145: + "' doesn't have a default transition");
146: }
147: signal(node.getDefaultLeavingTransition(),
148: new ExecutionContext(this ));
149: }
150:
151: /**
152: * provides a signal to the token. this leave the current state over the given
153: * transition name.
154: */
155: public void signal(String transitionName) {
156: if (node == null) {
157: throw new JbpmException(
158: "token '"
159: + this
160: + "' can't be signalled cause it is currently not positioned in a node");
161: }
162: if (node.getDefaultLeavingTransition() == null) {
163: throw new JbpmException("couldn't signal token '" + this
164: + "' : node '" + node
165: + "' doesn't have a default transition");
166: }
167: Transition leavingTransition = node
168: .getLeavingTransition(transitionName);
169: if (leavingTransition == null) {
170: throw new JbpmException("transition '" + transitionName
171: + "' does not exist on " + node);
172: }
173: signal(leavingTransition, new ExecutionContext(this ));
174: }
175:
176: /**
177: * provides a signal to the token. this leave the current state over the given
178: * transition name.
179: */
180: public void signal(Transition transition) {
181: signal(transition, new ExecutionContext(this ));
182: }
183:
184: void signal(ExecutionContext executionContext) {
185: signal(node.getDefaultLeavingTransition(), executionContext);
186: }
187:
188: void signal(Transition transition, ExecutionContext executionContext) {
189: if (transition == null) {
190: throw new JbpmException(
191: "couldn't signal without specifying a leaving transition : transition is null");
192: }
193: if (executionContext == null) {
194: throw new JbpmException(
195: "couldn't signal without an execution context: executionContext is null");
196: }
197: if (isSuspended) {
198: throw new JbpmException("can't signal token '" + name
199: + "' (" + id + "): it is suspended");
200: }
201: if (isLocked()) {
202: throw new JbpmException("this token is locked by " + lock);
203: }
204:
205: startCompositeLog(new SignalLog(transition));
206: try {
207: // fire the event before-signal
208: Node signalNode = node;
209: signalNode.fireEvent(Event.EVENTTYPE_BEFORE_SIGNAL,
210: executionContext);
211:
212: // start calculating the next state
213: node.leave(executionContext, transition);
214:
215: // if required, check if this token is implicitly terminated
216: checkImplicitTermination();
217:
218: // fire the event after-signal
219: signalNode.fireEvent(Event.EVENTTYPE_AFTER_SIGNAL,
220: executionContext);
221:
222: } finally {
223: endCompositeLog();
224: }
225: }
226:
227: /**
228: * a set of all the leaving transitions on the current node for which the condition expression resolves to true.
229: */
230: public Set getAvailableTransitions() {
231: Set availableTransitions = new HashSet();
232: if (node != null) {
233: addAvailableTransitionsOfNode(node, availableTransitions);
234: }
235: return availableTransitions;
236: }
237:
238: /**
239: * adds available transitions of that node to the Set
240: * and after that calls itself recursivly for the SuperSate of the Node
241: * if it has a super state
242: */
243: private void addAvailableTransitionsOfNode(Node currentNode,
244: Set availableTransitions) {
245: List leavingTransitions = currentNode.getLeavingTransitions();
246: if (leavingTransitions != null) {
247: Iterator iter = leavingTransitions.iterator();
248: while (iter.hasNext()) {
249: Transition transition = (Transition) iter.next();
250: String conditionExpression = transition.getCondition();
251: if (conditionExpression != null) {
252: Object result = JbpmExpressionEvaluator.evaluate(
253: conditionExpression, new ExecutionContext(
254: this ));
255: if ((result instanceof Boolean)
256: && (((Boolean) result).booleanValue())) {
257: availableTransitions.add(transition);
258: }
259: } else {
260: availableTransitions.add(transition);
261: }
262: }
263: }
264: if (currentNode.getSuperState() != null) {
265: addAvailableTransitionsOfNode(currentNode.getSuperState(),
266: availableTransitions);
267: }
268: }
269:
270: /**
271: * ends this token and all of its children (if any). this is the last active (=not-ended) child of a parent token,
272: * the parent token will be ended as well and that verification will continue to
273: * propagate.
274: */
275: public void end() {
276: end(true);
277: }
278:
279: /**
280: * ends this token with optional parent ending verification.
281: * @param verifyParentTermination specifies if the parent token should be checked for termination.
282: * if verifyParentTermination is set to true and this is the last non-ended child of a parent token,
283: * the parent token will be ended as well and the verification will continue to propagate.
284: */
285: public void end(boolean verifyParentTermination) {
286: // if not already ended
287: if (end == null) {
288:
289: // ended tokens cannot reactivate parents
290: isAbleToReactivateParent = false;
291:
292: // set the end date
293: // the end date is also the flag that indicates that this token has ended.
294: this .end = Clock.getCurrentTime();
295:
296: // end all this token's children
297: if (children != null) {
298: Iterator iter = children.values().iterator();
299: while (iter.hasNext()) {
300: Token child = (Token) iter.next();
301: if (!child.hasEnded()) {
302: child.end();
303: }
304: }
305: }
306:
307: if (subProcessInstance != null) {
308: subProcessInstance.end();
309: }
310:
311: // only log the end of child-tokens. the process instance logs replace the root token logs.
312: if (parent != null) {
313: // add a log
314: parent.addLog(new TokenEndLog(this ));
315: }
316:
317: // if there are tasks associated to this token, remove signalling capabilities
318: TaskMgmtInstance taskMgmtInstance = (processInstance != null ? processInstance
319: .getTaskMgmtInstance()
320: : null);
321: if (taskMgmtInstance != null) {
322: taskMgmtInstance.removeSignalling(this );
323: }
324:
325: if (verifyParentTermination) {
326: // if this is the last active token of the parent,
327: // the parent needs to be ended as well
328: notifyParentOfTokenEnd();
329: }
330: }
331: }
332:
333: // comments /////////////////////////////////////////////////////////////////
334:
335: public void addComment(String message) {
336: addComment(new Comment(message));
337: }
338:
339: public void addComment(Comment comment) {
340: if (comments == null)
341: comments = new ArrayList();
342: comments.add(comment);
343: comment.setToken(this );
344: }
345:
346: public List getComments() {
347: return comments;
348: }
349:
350: // operations helper methods ////////////////////////////////////////////////
351:
352: /**
353: * notifies a parent that one of its nodeMap has ended.
354: */
355: void notifyParentOfTokenEnd() {
356: if (isRoot()) {
357: processInstance.end();
358: } else {
359:
360: if (!parent.hasActiveChildren()) {
361: parent.end();
362: }
363: }
364: }
365:
366: /**
367: * tells if this token has child tokens that have not yet ended.
368: */
369: public boolean hasActiveChildren() {
370: boolean foundActiveChildToken = false;
371: // try and find at least one child token that is
372: // still active (= not ended)
373: if (children != null) {
374: Iterator iter = children.values().iterator();
375: while ((iter.hasNext()) && (!foundActiveChildToken)) {
376: Token child = (Token) iter.next();
377: if (!child.hasEnded()) {
378: foundActiveChildToken = true;
379: }
380: }
381: }
382: return foundActiveChildToken;
383: }
384:
385: // log convenience methods //////////////////////////////////////////////////
386:
387: /**
388: * convenience method for adding a process log.
389: */
390: public void addLog(ProcessLog processLog) {
391: LoggingInstance li = (LoggingInstance) processInstance
392: .getInstance(LoggingInstance.class);
393: if (li != null) {
394: processLog.setToken(this );
395: li.addLog(processLog);
396: }
397: }
398:
399: /**
400: * convenience method for starting a composite log. When you add composite logs,
401: * make sure you put the {@link #endCompositeLog()} in a finally block.
402: */
403: public void startCompositeLog(CompositeLog compositeLog) {
404: LoggingInstance li = (LoggingInstance) processInstance
405: .getInstance(LoggingInstance.class);
406: if (li != null) {
407: compositeLog.setToken(this );
408: li.startCompositeLog(compositeLog);
409: }
410: }
411:
412: /**
413: * convenience method for ending a composite log. Make sure you put this in a finally block.
414: */
415: public void endCompositeLog() {
416: LoggingInstance li = (LoggingInstance) processInstance
417: .getInstance(LoggingInstance.class);
418: if (li != null) {
419: li.endCompositeLog();
420: }
421: }
422:
423: // various information extraction methods ///////////////////////////////////
424:
425: public String toString() {
426: return "Token(" + getFullName() + ")";
427: }
428:
429: public boolean hasEnded() {
430: return (end != null);
431: }
432:
433: public boolean isRoot() {
434: return (parent == null);
435: }
436:
437: public boolean hasParent() {
438: return (parent != null);
439: }
440:
441: public boolean hasChild(String name) {
442: return (children != null ? children.containsKey(name) : false);
443: }
444:
445: public Token getChild(String name) {
446: Token child = null;
447: if (children != null) {
448: child = (Token) children.get(name);
449: }
450: return child;
451: }
452:
453: public String getFullName() {
454: if (parent == null)
455: return "/";
456: if (parent.getParent() == null)
457: return "/" + name;
458: return parent.getFullName() + "/" + name;
459: }
460:
461: public List getChildrenAtNode(Node aNode) {
462: List foundChildren = new ArrayList();
463: getChildrenAtNode(aNode, foundChildren);
464: return foundChildren;
465: }
466:
467: void getChildrenAtNode(Node aNode, List foundTokens) {
468: if (aNode.equals(node)) {
469: foundTokens.add(this );
470: } else if (children != null && !children.isEmpty()) {
471: for (Iterator it = children.values().iterator(); it
472: .hasNext();) {
473: Token aChild = (Token) it.next();
474: aChild.getChildrenAtNode(aNode, foundTokens);
475: }
476: }
477: }
478:
479: public void collectChildrenRecursively(List tokens) {
480: if (children != null) {
481: Iterator iter = children.values().iterator();
482: while (iter.hasNext()) {
483: Token child = (Token) iter.next();
484: tokens.add(child);
485: child.collectChildrenRecursively(tokens);
486: }
487: }
488: }
489:
490: public Token findToken(String relativeTokenPath) {
491: if (relativeTokenPath == null)
492: return null;
493: String path = relativeTokenPath.trim();
494: if (("".equals(path)) || (".".equals(path))) {
495: return this ;
496: }
497: if ("..".equals(path)) {
498: return parent;
499: }
500: if (path.startsWith("/")) {
501: Token root = processInstance.getRootToken();
502: return root.findToken(path.substring(1));
503: }
504: if (path.startsWith("./")) {
505: return findToken(path.substring(2));
506: }
507: if (path.startsWith("../")) {
508: if (parent != null) {
509: return parent.findToken(path.substring(3));
510: }
511: return null;
512: }
513: int slashIndex = path.indexOf('/');
514: if (slashIndex == -1) {
515: return (Token) (children != null ? children.get(path)
516: : null);
517: }
518: Token token = null;
519: String name = path.substring(0, slashIndex);
520: token = (Token) children.get(name);
521: if (token != null) {
522: return token.findToken(path.substring(slashIndex + 1));
523: }
524: return null;
525: }
526:
527: public Map getActiveChildren() {
528: Map activeChildren = new HashMap();
529: if (children != null) {
530: Iterator iter = children.entrySet().iterator();
531: while (iter.hasNext()) {
532: Map.Entry entry = (Map.Entry) iter.next();
533: Token child = (Token) entry.getValue();
534: if (!child.hasEnded()) {
535: String childName = (String) entry.getKey();
536: activeChildren.put(childName, child);
537: }
538: }
539: }
540: return activeChildren;
541: }
542:
543: public void checkImplicitTermination() {
544: if (isTerminationImplicit && node.hasNoLeavingTransitions()) {
545: end();
546:
547: if (processInstance.isTerminatedImplicitly()) {
548: processInstance.end();
549: }
550: }
551: }
552:
553: public boolean isTerminatedImplicitly() {
554: if (end != null)
555: return true;
556:
557: Map leavingTransitions = node.getLeavingTransitionsMap();
558: if ((leavingTransitions != null)
559: && (leavingTransitions.size() > 0)) {
560: // ok: found a non-terminated token
561: return false;
562: }
563:
564: // loop over all active child tokens
565: Iterator iter = getActiveChildren().values().iterator();
566: while (iter.hasNext()) {
567: Token child = (Token) iter.next();
568: if (!child.isTerminatedImplicitly()) {
569: return false;
570: }
571: }
572: // if none of the above, this token is terminated implicitly
573: return true;
574: }
575:
576: public int nextLogIndex() {
577: return nextLogIndex++;
578: }
579:
580: /**
581: * suspends a process execution.
582: */
583: public void suspend() {
584: isSuspended = true;
585:
586: suspendJobs();
587: suspendTaskInstances();
588:
589: // propagate to child tokens
590: if (children != null) {
591: Iterator iter = children.values().iterator();
592: while (iter.hasNext()) {
593: Token child = (Token) iter.next();
594: child.suspend();
595: }
596: }
597: }
598:
599: void suspendJobs() {
600: JbpmContext jbpmContext = JbpmContext.getCurrentJbpmContext();
601: JobSession jobSession = (jbpmContext != null ? jbpmContext
602: .getJobSession() : null);
603: if (jobSession != null) {
604: jobSession.suspendJobs(this );
605: }
606: }
607:
608: void suspendTaskInstances() {
609: TaskMgmtInstance taskMgmtInstance = (processInstance != null ? processInstance
610: .getTaskMgmtInstance()
611: : null);
612: if (taskMgmtInstance != null) {
613: taskMgmtInstance.suspend(this );
614: }
615: }
616:
617: /**
618: * resumes a process execution.
619: */
620: public void resume() {
621: isSuspended = false;
622:
623: resumeJobs();
624: resumeTaskInstances();
625:
626: // propagate to child tokens
627: if (children != null) {
628: Iterator iter = children.values().iterator();
629: while (iter.hasNext()) {
630: Token child = (Token) iter.next();
631: child.resume();
632: }
633: }
634: }
635:
636: void resumeJobs() {
637: JbpmContext jbpmContext = JbpmContext.getCurrentJbpmContext();
638: JobSession jobSession = (jbpmContext != null ? jbpmContext
639: .getJobSession() : null);
640: if (jobSession != null) {
641: jobSession.resumeJobs(this );
642: }
643: }
644:
645: void resumeTaskInstances() {
646: TaskMgmtInstance taskMgmtInstance = (processInstance != null ? processInstance
647: .getTaskMgmtInstance()
648: : null);
649: if (taskMgmtInstance != null) {
650: taskMgmtInstance.resume(this );
651: }
652: }
653:
654: // equals ///////////////////////////////////////////////////////////////////
655: // hack to support comparing hibernate proxies against the real objects
656: // since this always falls back to ==, we don't need to overwrite the hashcode
657: public boolean equals(Object o) {
658: return EqualsUtil.equals(this , o);
659: }
660:
661: public ProcessInstance createSubProcessInstance(
662: ProcessDefinition subProcessDefinition) {
663: // create the new sub process instance
664: subProcessInstance = new ProcessInstance(subProcessDefinition);
665: // bind the subprocess to the super-process-token
666: setSubProcessInstance(subProcessInstance);
667: subProcessInstance.setSuperProcessToken(this );
668: // make sure the process gets saved during super process save
669: processInstance.addCascadeProcessInstance(subProcessInstance);
670: return subProcessInstance;
671: }
672:
673: /**
674: * locks a process instance for further execution. A locked token
675: * cannot continue execution. This is a non-persistent
676: * operation. This is used to prevent tokens being propagated during
677: * the execution of actions.
678: * @see #unlock(String)
679: */
680: public void lock(String lockOwnerId) {
681: if (lockOwnerId == null) {
682: throw new JbpmException(
683: "can't lock with null value for the lockOwnerId");
684: }
685: if ((lock != null) && (!lock.equals(lockOwnerId))) {
686: throw new JbpmException("token '" + id
687: + "' can't be locked by '" + lockOwnerId
688: + "' cause it's already locked by '" + lock + "'");
689: }
690: log.debug("token[" + id + "] is locked by " + lockOwnerId);
691: lock = lockOwnerId;
692: }
693:
694: /**
695: * @see #lock(String)
696: */
697: public void unlock(String lockOwnerId) {
698: if (lock == null) {
699: log.warn("lock owner '" + lockOwnerId
700: + "' tries to unlock token '" + id
701: + "' which is not locked");
702: } else if (!lock.equals(lockOwnerId)) {
703: throw new JbpmException("'" + lockOwnerId
704: + "' can't unlock token '" + id
705: + "' because it was already locked by '" + lock
706: + "'");
707: }
708: log.debug("token[" + id + "] is unlocked by " + lockOwnerId);
709: lock = null;
710: }
711:
712: public boolean isLocked() {
713: return lock != null;
714: }
715:
716: // getters and setters //////////////////////////////////////////////////////
717:
718: public long getId() {
719: return id;
720: }
721:
722: public Date getStart() {
723: return start;
724: }
725:
726: public Date getEnd() {
727: return end;
728: }
729:
730: public String getName() {
731: return name;
732: }
733:
734: public ProcessInstance getProcessInstance() {
735: return processInstance;
736: }
737:
738: public Map getChildren() {
739: return children;
740: }
741:
742: public Node getNode() {
743: return node;
744: }
745:
746: public void setNode(Node node) {
747: this .node = node;
748: }
749:
750: public Token getParent() {
751: return parent;
752: }
753:
754: public void setParent(Token parent) {
755: this .parent = parent;
756: }
757:
758: public void setProcessInstance(ProcessInstance processInstance) {
759: this .processInstance = processInstance;
760: }
761:
762: public ProcessInstance getSubProcessInstance() {
763: return subProcessInstance;
764: }
765:
766: public Date getNodeEnter() {
767: return nodeEnter;
768: }
769:
770: public void setNodeEnter(Date nodeEnter) {
771: this .nodeEnter = nodeEnter;
772: }
773:
774: public boolean isAbleToReactivateParent() {
775: return isAbleToReactivateParent;
776: }
777:
778: public void setAbleToReactivateParent(
779: boolean isAbleToReactivateParent) {
780: this .isAbleToReactivateParent = isAbleToReactivateParent;
781: }
782:
783: public boolean isTerminationImplicit() {
784: return isTerminationImplicit;
785: }
786:
787: public void setTerminationImplicit(boolean isTerminationImplicit) {
788: this .isTerminationImplicit = isTerminationImplicit;
789: }
790:
791: public boolean isSuspended() {
792: return isSuspended;
793: }
794:
795: public void setSubProcessInstance(ProcessInstance subProcessInstance) {
796: this .subProcessInstance = subProcessInstance;
797: }
798:
799: private static final Log log = LogFactory.getLog(Token.class);
800: }
|