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.def;
023:
024: import java.util.ArrayList;
025: import java.util.Date;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.ListIterator;
031: import java.util.Map;
032: import java.util.Set;
033:
034: import org.dom4j.Element;
035: import org.jbpm.JbpmException;
036: import org.jbpm.graph.action.ActionTypes;
037: import org.jbpm.graph.exe.ExecutionContext;
038: import org.jbpm.graph.exe.Token;
039: import org.jbpm.graph.log.NodeLog;
040: import org.jbpm.job.ExecuteNodeJob;
041: import org.jbpm.job.Job;
042: import org.jbpm.jpdl.xml.JpdlXmlReader;
043: import org.jbpm.jpdl.xml.Parsable;
044: import org.jbpm.msg.MessageService;
045: import org.jbpm.svc.Services;
046: import org.jbpm.util.Clock;
047:
048: public class Node extends GraphElement implements Parsable {
049:
050: private static final long serialVersionUID = 1L;
051:
052: protected List leavingTransitions = null;
053: transient Map leavingTransitionMap = null;
054: protected Set arrivingTransitions = null;
055: protected Action action = null;
056: protected SuperState super State = null;
057: protected boolean isAsync = false;
058: protected boolean isAsyncExclusive = false;
059:
060: // event types //////////////////////////////////////////////////////////////
061:
062: public static final String[] supportedEventTypes = new String[] {
063: Event.EVENTTYPE_NODE_ENTER, Event.EVENTTYPE_NODE_LEAVE,
064: Event.EVENTTYPE_BEFORE_SIGNAL, Event.EVENTTYPE_AFTER_SIGNAL };
065:
066: public String[] getSupportedEventTypes() {
067: return supportedEventTypes;
068: }
069:
070: // constructors /////////////////////////////////////////////////////////////
071:
072: /**
073: * creates an unnamed node.
074: */
075: public Node() {
076: }
077:
078: /**
079: * creates a node with the given name.
080: */
081: public Node(String name) {
082: super (name);
083: }
084:
085: public void read(Element nodeElement, JpdlXmlReader jpdlXmlReader) {
086: action = jpdlXmlReader.readSingleAction(nodeElement);
087: }
088:
089: public void write(Element nodeElement) {
090: if (action != null) {
091: String actionName = ActionTypes.getActionName(action
092: .getClass());
093: Element actionElement = nodeElement.addElement(actionName);
094: action.write(actionElement);
095: }
096: }
097:
098: // leaving transitions //////////////////////////////////////////////////////
099:
100: public List getLeavingTransitions() {
101: return leavingTransitions;
102: }
103:
104: /**
105: * are the leaving {@link Transition}s, mapped by their name (java.lang.String).
106: */
107: public Map getLeavingTransitionsMap() {
108: if ((leavingTransitionMap == null)
109: && (leavingTransitions != null)) {
110: // initialize the cached leaving transition map
111: leavingTransitionMap = new HashMap();
112: ListIterator iter = leavingTransitions
113: .listIterator(leavingTransitions.size());
114: while (iter.hasPrevious()) {
115: Transition leavingTransition = (Transition) iter
116: .previous();
117: leavingTransitionMap.put(leavingTransition.getName(),
118: leavingTransition);
119: }
120: }
121: return leavingTransitionMap;
122: }
123:
124: /**
125: * creates a bidirection relation between this node and the given leaving transition.
126: * @throws IllegalArgumentException if leavingTransition is null.
127: */
128: public Transition addLeavingTransition(Transition leavingTransition) {
129: if (leavingTransition == null)
130: throw new IllegalArgumentException(
131: "can't add a null leaving transition to an node");
132: if (leavingTransitions == null)
133: leavingTransitions = new ArrayList();
134: leavingTransitions.add(leavingTransition);
135: leavingTransition.from = this ;
136: leavingTransitionMap = null;
137: return leavingTransition;
138: }
139:
140: /**
141: * removes the bidirection relation between this node and the given leaving transition.
142: * @throws IllegalArgumentException if leavingTransition is null.
143: */
144: public void removeLeavingTransition(Transition leavingTransition) {
145: if (leavingTransition == null)
146: throw new IllegalArgumentException(
147: "can't remove a null leavingTransition from an node");
148: if (leavingTransitions != null) {
149: if (leavingTransitions.remove(leavingTransition)) {
150: leavingTransition.from = null;
151: leavingTransitionMap = null;
152: }
153: }
154: }
155:
156: /**
157: * checks for the presence of a leaving transition with the given name.
158: * @return true if this node has a leaving transition with the given name,
159: * false otherwise.
160: */
161: public boolean hasLeavingTransition(String transitionName) {
162: if (leavingTransitions == null)
163: return false;
164: return getLeavingTransitionsMap().containsKey(transitionName);
165: }
166:
167: /**
168: * retrieves a leaving transition by name. note that also the leaving
169: * transitions of the supernode are taken into account.
170: */
171: public Transition getLeavingTransition(String transitionName) {
172: Transition transition = null;
173: if (leavingTransitions != null) {
174: transition = (Transition) getLeavingTransitionsMap().get(
175: transitionName);
176: }
177: if ((transition == null) && (super State != null)) {
178: transition = super State
179: .getLeavingTransition(transitionName);
180: }
181: return transition;
182: }
183:
184: /**
185: * true if this transition has leaving transitions.
186: */
187: public boolean hasNoLeavingTransitions() {
188: return (((leavingTransitions == null) || (leavingTransitions
189: .size() == 0)) && ((super State == null) || (super State
190: .hasNoLeavingTransitions())));
191: }
192:
193: /**
194: * generates a new name for a transition that will be added as a leaving transition.
195: */
196: public String generateNextLeavingTransitionName() {
197: String name = null;
198: if (leavingTransitions != null) {
199: if (!containsName(leavingTransitions, null)) {
200: name = null;
201: } else {
202: int n = 1;
203: while (containsName(leavingTransitions, Integer
204: .toString(n)))
205: n++;
206: name = Integer.toString(n);
207: }
208: }
209: return name;
210: }
211:
212: boolean containsName(List leavingTransitions, String name) {
213: Iterator iter = leavingTransitions.iterator();
214: while (iter.hasNext()) {
215: Transition transition = (Transition) iter.next();
216: if ((name == null) && (transition.getName() == null)) {
217: return true;
218: } else if ((name != null)
219: && (name.equals(transition.getName()))) {
220: return true;
221: }
222: }
223: return false;
224: }
225:
226: // default leaving transition and leaving transition ordering ///////////////
227:
228: /**
229: * is the default leaving transition.
230: */
231: public Transition getDefaultLeavingTransition() {
232: Transition defaultTransition = null;
233: if ((leavingTransitions != null)
234: && (leavingTransitions.size() > 0)) {
235: defaultTransition = (Transition) leavingTransitions.get(0);
236: } else if (super State != null) {
237: defaultTransition = super State
238: .getDefaultLeavingTransition();
239: }
240: return defaultTransition;
241: }
242:
243: /**
244: * moves one leaving transition from the oldIndex and inserts it at the newIndex.
245: */
246: public void reorderLeavingTransition(int oldIndex, int newIndex) {
247: if ((leavingTransitions != null)
248: && (Math.min(oldIndex, newIndex) >= 0)
249: && (Math.max(oldIndex, newIndex) < leavingTransitions
250: .size())) {
251: Object o = leavingTransitions.remove(oldIndex);
252: leavingTransitions.add(newIndex, o);
253: }
254: }
255:
256: public List getLeavingTransitionsList() {
257: return leavingTransitions;
258: }
259:
260: // arriving transitions /////////////////////////////////////////////////////
261:
262: /**
263: * are the arriving transitions.
264: */
265: public Set getArrivingTransitions() {
266: return arrivingTransitions;
267: }
268:
269: /**
270: * add a bidirection relation between this node and the given arriving
271: * transition.
272: * @throws IllegalArgumentException if t is null.
273: */
274: public Transition addArrivingTransition(
275: Transition arrivingTransition) {
276: if (arrivingTransition == null)
277: throw new IllegalArgumentException(
278: "can't add a null arrivingTransition to a node");
279: if (arrivingTransitions == null)
280: arrivingTransitions = new HashSet();
281: arrivingTransitions.add(arrivingTransition);
282: arrivingTransition.to = this ;
283: return arrivingTransition;
284: }
285:
286: /**
287: * removes the bidirection relation between this node and the given arriving
288: * transition.
289: * @throws IllegalArgumentException if t is null.
290: */
291: public void removeArrivingTransition(Transition arrivingTransition) {
292: if (arrivingTransition == null)
293: throw new IllegalArgumentException(
294: "can't remove a null arrivingTransition from a node");
295: if (arrivingTransitions != null) {
296: if (arrivingTransitions.remove(arrivingTransition)) {
297: arrivingTransition.to = null;
298: }
299: }
300: }
301:
302: // various //////////////////////////////////////////////////////////////////
303:
304: /**
305: * is the {@link SuperState} or the {@link ProcessDefinition} in which this
306: * node is contained.
307: */
308: public GraphElement getParent() {
309: GraphElement parent = processDefinition;
310: if (super State != null)
311: parent = super State;
312: return parent;
313: }
314:
315: // behaviour methods ////////////////////////////////////////////////////////
316:
317: /**
318: * called by a transition to pass execution to this node.
319: */
320: public void enter(ExecutionContext executionContext) {
321: Token token = executionContext.getToken();
322:
323: // update the runtime context information
324: token.setNode(this );
325:
326: // fire the leave-node event for this node
327: fireEvent(Event.EVENTTYPE_NODE_ENTER, executionContext);
328:
329: // keep track of node entrance in the token, so that a node-log can be generated at node leave time.
330: token.setNodeEnter(Clock.getCurrentTime());
331:
332: // remove the transition references from the runtime context
333: executionContext.setTransition(null);
334: executionContext.setTransitionSource(null);
335:
336: // execute the node
337: if (isAsync) {
338: ExecuteNodeJob job = createAsyncContinuationJob(token);
339: MessageService messageService = (MessageService) Services
340: .getCurrentService(Services.SERVICENAME_MESSAGE);
341: messageService.send(job);
342: token.lock(job.toString());
343: } else {
344: execute(executionContext);
345: }
346: }
347:
348: protected ExecuteNodeJob createAsyncContinuationJob(Token token) {
349: ExecuteNodeJob job = new ExecuteNodeJob(token);
350: job.setNode(this );
351: job.setDueDate(new Date());
352: job.setExclusive(isAsyncExclusive);
353: return job;
354: }
355:
356: /**
357: * override this method to customize the node behaviour.
358: */
359: public void execute(ExecutionContext executionContext) {
360: // if there is a custom action associated with this node
361: if (action != null) {
362: try {
363: // execute the action
364: executeAction(action, executionContext);
365:
366: } catch (Exception exception) {
367: // NOTE that Error's are not caught because that might halt the JVM and mask the original Error.
368: // search for an exception handler or throw to the client
369: raiseException(exception, executionContext);
370: }
371:
372: } else {
373: // let this node handle the token
374: // the default behaviour is to leave the node over the default transition.
375: leave(executionContext);
376: }
377: }
378:
379: /**
380: * called by the implementation of this node to continue execution over the default transition.
381: */
382: public void leave(ExecutionContext executionContext) {
383: leave(executionContext, getDefaultLeavingTransition());
384: }
385:
386: /**
387: * called by the implementation of this node to continue execution over the specified transition.
388: */
389: public void leave(ExecutionContext executionContext,
390: String transitionName) {
391: Transition transition = getLeavingTransition(transitionName);
392: if (transition == null) {
393: throw new JbpmException("transition '" + transitionName
394: + "' is not a leaving transition of node '" + this
395: + "'");
396: }
397: leave(executionContext, transition);
398: }
399:
400: /**
401: * called by the implementation of this node to continue execution over the given transition.
402: */
403: public void leave(ExecutionContext executionContext,
404: Transition transition) {
405: if (transition == null)
406: throw new JbpmException("can't leave node '" + this
407: + "' without leaving transition");
408: Token token = executionContext.getToken();
409: token.setNode(this );
410: executionContext.setTransition(transition);
411:
412: // fire the leave-node event for this node
413: fireEvent(Event.EVENTTYPE_NODE_LEAVE, executionContext);
414:
415: // log this node
416: if (token.getNodeEnter() != null) {
417: addNodeLog(token);
418: }
419:
420: // update the runtime information for taking the transition
421: // the transitionSource is used to calculate events on superstates
422: executionContext.setTransitionSource(this );
423:
424: // take the transition
425: transition.take(executionContext);
426: }
427:
428: protected void addNodeLog(Token token) {
429: token.addLog(new NodeLog(this , token.getNodeEnter(), Clock
430: .getCurrentTime()));
431: }
432:
433: /////////////////////////////////////////////////////////////////////////////
434:
435: public ProcessDefinition getProcessDefinition() {
436: ProcessDefinition pd = this .processDefinition;
437: if (super State != null) {
438: pd = super State.getProcessDefinition();
439: }
440: return pd;
441: }
442:
443: // change the name of a node ////////////////////////////////////////////////
444: /**
445: * updates the name of this node
446: */
447: public void setName(String name) {
448: if (isDifferent(this .name, name)) {
449: String oldName = this .name;
450: if (super State != null) {
451: if (super State.hasNode(name)) {
452: throw new IllegalArgumentException(
453: "couldn't set name '"
454: + name
455: + "' on node '"
456: + this
457: + "'cause the superState of this node has already another child node with the same name");
458: }
459: Map nodes = super State.getNodesMap();
460: nodes.remove(oldName);
461: nodes.put(name, this );
462: } else if (processDefinition != null) {
463: if (processDefinition.hasNode(name)) {
464: throw new IllegalArgumentException(
465: "couldn't set name '"
466: + name
467: + "' on node '"
468: + this
469: + "'cause the process definition of this node has already another node with the same name");
470: }
471: Map nodeMap = processDefinition.getNodesMap();
472: nodeMap.remove(oldName);
473: nodeMap.put(name, this );
474: }
475: this .name = name;
476: }
477: }
478:
479: boolean isDifferent(String name1, String name2) {
480: if ((name1 != null) && (name1.equals(name2))) {
481: return false;
482: } else if ((name1 == null) && (name2 == null)) {
483: return false;
484: }
485: return true;
486: }
487:
488: /**
489: * the slash separated name that includes all the superstate names.
490: */
491: public String getFullyQualifiedName() {
492: String fullyQualifiedName = name;
493: if (super State != null) {
494: fullyQualifiedName = super State.getFullyQualifiedName()
495: + "/" + name;
496: }
497: return fullyQualifiedName;
498: }
499:
500: /** indicates wether this node is a superstate. */
501: public boolean isSuperStateNode() {
502: return false;
503: }
504:
505: /** returns a list of child nodes (only applicable for {@link SuperState})s. */
506: public List getNodes() {
507: return null;
508: }
509:
510: // getters and setters //////////////////////////////////////////////////////
511:
512: public SuperState getSuperState() {
513: return super State;
514: }
515:
516: public Action getAction() {
517: return action;
518: }
519:
520: public void setAction(Action action) {
521: this .action = action;
522: }
523:
524: public boolean isAsync() {
525: return isAsync;
526: }
527:
528: public void setAsync(boolean isAsync) {
529: this .isAsync = isAsync;
530: }
531:
532: public boolean isAsyncExclusive() {
533: return isAsyncExclusive;
534: }
535:
536: public void setAsyncExclusive(boolean isAsyncExclusive) {
537: this.isAsyncExclusive = isAsyncExclusive;
538: }
539: }
|