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.Collections;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029:
030: import org.jbpm.JbpmException;
031: import org.jbpm.graph.exe.ExecutionContext;
032: import org.jbpm.graph.exe.Token;
033: import org.jbpm.graph.log.TransitionLog;
034: import org.jbpm.jpdl.el.impl.JbpmExpressionEvaluator;
035:
036: public class Transition extends GraphElement {
037:
038: private static final long serialVersionUID = 1L;
039:
040: protected Node from = null;
041: protected Node to = null;
042: protected String condition = null;
043: transient boolean isConditionEnforced = true;
044:
045: // event types //////////////////////////////////////////////////////////////
046:
047: public static final String[] supportedEventTypes = new String[] { Event.EVENTTYPE_TRANSITION };
048:
049: public String[] getSupportedEventTypes() {
050: return supportedEventTypes;
051: }
052:
053: // constructors /////////////////////////////////////////////////////////////
054:
055: public Transition() {
056: }
057:
058: public Transition(String name) {
059: super (name);
060: }
061:
062: // from /////////////////////////////////////////////////////////////////////
063:
064: public Node getFrom() {
065: return from;
066: }
067:
068: /**
069: * sets the from node unidirectionally. use {@link Node#addLeavingTransition(Transition)}
070: * to get bidirectional relations mgmt.
071: */
072: public void setFrom(Node from) {
073: this .from = from;
074: }
075:
076: // to ///////////////////////////////////////////////////////////////////////
077:
078: /**
079: * sets the to node unidirectionally. use {@link Node#addArrivingTransition(Transition)}
080: * to get bidirectional relations mgmt.
081: */
082: public void setTo(Node to) {
083: this .to = to;
084: }
085:
086: public Node getTo() {
087: return to;
088: }
089:
090: /**
091: * the condition expresssion for this transition.
092: */
093: public String getCondition() {
094: return condition;
095: }
096:
097: public void setCondition(String conditionExpression) {
098: this .condition = conditionExpression;
099: }
100:
101: public void removeConditionEnforcement() {
102: isConditionEnforced = false;
103: }
104:
105: // behaviour ////////////////////////////////////////////////////////////////
106:
107: /**
108: * passes execution over this transition.
109: */
110: public void take(ExecutionContext executionContext) {
111: // update the runtime context information
112: executionContext.getToken().setNode(null);
113:
114: Token token = executionContext.getToken();
115:
116: if ((condition != null) && (isConditionEnforced)) {
117: Object result = JbpmExpressionEvaluator.evaluate(condition,
118: executionContext);
119: if (result == null) {
120: throw new JbpmException("transition condition "
121: + condition + " evaluated to null");
122: } else if (!(result instanceof Boolean)) {
123: throw new JbpmException("transition condition "
124: + condition + " evaluated to non-boolean: "
125: + result.getClass().getName());
126: } else if (!((Boolean) result).booleanValue()) {
127: throw new JbpmException("transition condition "
128: + condition + " evaluated to 'false'");
129: }
130: }
131:
132: // start the transition log
133: TransitionLog transitionLog = new TransitionLog(this ,
134: executionContext.getTransitionSource());
135: token.startCompositeLog(transitionLog);
136: try {
137:
138: // fire leave events for superstates (if any)
139: fireSuperStateLeaveEvents(executionContext);
140:
141: // fire the transition event (if any)
142: fireEvent(Event.EVENTTYPE_TRANSITION, executionContext);
143:
144: // fire enter events for superstates (if any)
145: Node destination = fireSuperStateEnterEvents(executionContext);
146: // update the ultimate destinationNode of this transition
147: transitionLog.setDestinationNode(destination);
148:
149: } finally {
150: // end the transition log
151: token.endCompositeLog();
152: }
153:
154: // pass the token to the destinationNode node
155: to.enter(executionContext);
156: }
157:
158: Node fireSuperStateEnterEvents(ExecutionContext executionContext) {
159: // calculate the actual destinationNode node
160: Node destination = to;
161: while (destination.isSuperStateNode()) {
162: destination = (Node) destination.getNodes().get(0);
163: }
164:
165: if (destination == null) {
166: String transitionName = (name != null ? "'" + name + "'"
167: : "in node '" + from + "'");
168: throw new JbpmException(
169: "transition "
170: + transitionName
171: + " doesn't have destination. check your processdefinition.xml");
172: }
173:
174: // performance optimisation: check if at least there is a candidate superstate to be entered.
175: if (destination.getSuperState() != null) {
176: // collect all the superstates being left
177: List leavingSuperStates = collectAllSuperStates(
178: destination, from);
179: // reverse the order so that events are fired from outer to inner superstates
180: Collections.reverse(leavingSuperStates);
181: // fire a superstate-enter event for all superstates being left
182: fireSuperStateEvents(leavingSuperStates,
183: Event.EVENTTYPE_SUPERSTATE_ENTER, executionContext);
184: }
185:
186: return destination;
187: }
188:
189: void fireSuperStateLeaveEvents(ExecutionContext executionContext) {
190: // performance optimisation: check if at least there is a candidate superstate to be left.
191: if (executionContext.getTransitionSource().getSuperState() != null) {
192: // collect all the superstates being left
193: List leavingSuperStates = collectAllSuperStates(
194: executionContext.getTransitionSource(), to);
195: // fire a node-leave event for all superstates being left
196: fireSuperStateEvents(leavingSuperStates,
197: Event.EVENTTYPE_SUPERSTATE_LEAVE, executionContext);
198: }
199: }
200:
201: /**
202: * collect all superstates of a that do not contain node b.
203: */
204: static List collectAllSuperStates(Node a, Node b) {
205: SuperState super State = a.getSuperState();
206: List leavingSuperStates = new ArrayList();
207: while (super State != null) {
208: if (!super State.containsNode(b)) {
209: leavingSuperStates.add(super State);
210: super State = super State.getSuperState();
211: } else {
212: super State = null;
213: }
214: }
215: return leavingSuperStates;
216: }
217:
218: /**
219: * fires the give event on all the superstates in the list.
220: */
221: void fireSuperStateEvents(List super States, String eventType,
222: ExecutionContext executionContext) {
223: Iterator iter = super States.iterator();
224: while (iter.hasNext()) {
225: SuperState leavingSuperState = (SuperState) iter.next();
226: leavingSuperState.fireEvent(eventType, executionContext);
227: }
228: }
229:
230: // other
231: /////////////////////////////////////////////////////////////////////////////
232:
233: public void setName(String name) {
234: if (from != null) {
235: if (from.hasLeavingTransition(name)) {
236: throw new IllegalArgumentException(
237: "couldn't set name '"
238: + name
239: + "' on transition '"
240: + this
241: + "'cause the from-node of this transition has already another leaving transition with the same name");
242: }
243: Map fromLeavingTransitions = from
244: .getLeavingTransitionsMap();
245: fromLeavingTransitions.remove(this .name);
246: fromLeavingTransitions.put(name, this );
247: }
248: this .name = name;
249: }
250:
251: public GraphElement getParent() {
252: GraphElement parent = null;
253: if ((from != null) && (to != null)) {
254: if (from.equals(to)) {
255: parent = from.getParent();
256: } else {
257: List fromParentChain = from.getParentChain();
258: List toParentChain = to.getParentChain();
259: Iterator fromIter = fromParentChain.iterator();
260: while (fromIter.hasNext() && (parent == null)) {
261: GraphElement fromParent = (GraphElement) fromIter
262: .next();
263: Iterator toIter = toParentChain.iterator();
264: while (toIter.hasNext() && (parent == null)) {
265: GraphElement toParent = (GraphElement) toIter
266: .next();
267: if (fromParent == toParent) {
268: parent = fromParent;
269: }
270: }
271: }
272: }
273: }
274: return parent;
275: }
276: }
|