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.node;
023:
024: import java.util.ArrayList;
025: import java.util.Collection;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029:
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032: import org.dom4j.Element;
033: import org.jbpm.JbpmException;
034: import org.jbpm.graph.action.Script;
035: import org.jbpm.graph.def.Node;
036: import org.jbpm.graph.exe.ExecutionContext;
037: import org.jbpm.graph.exe.Token;
038: import org.jbpm.jpdl.xml.JpdlXmlReader;
039: import org.jbpm.jpdl.xml.Parsable;
040:
041: /**
042: * specifies configurable fork behaviour.
043: *
044: * <p>if this fork behaviour is not sufficient for your needs, consider
045: * writing your own custom TokenHandler.
046: * </p>
047: *
048: * <p>this forkhandler can be configured in 3 ways :
049: * <ul>
050: * <li>without configuration : in that case the fork will launch one
051: * new sub-token over each of the leaving tranisions of the fork
052: * node.
053: * </li>
054: * <li>a script : can be used to calculate a collection of transition
055: * names at runtime. if a script is configured, the script must have
056: * exactly one variable with 'write' access. that variable
057: * should be assigned a java.util.Collection in the script
058: * expression.
059: * </li>
060: * </ul>
061: * </p>
062: */
063: public class Fork extends Node implements Parsable {
064:
065: private static final long serialVersionUID = 1L;
066:
067: /**
068: * a script that calculates the transitionNames at runtime.
069: */
070: Script script = null;
071:
072: public Fork() {
073: }
074:
075: public Fork(String name) {
076: super (name);
077: }
078:
079: public void read(Element forkElement, JpdlXmlReader jpdlReader) {
080: Element scriptElement = forkElement.element("script");
081: if (scriptElement != null) {
082: log
083: .warn("KNOWN LIMITATION: the script in a fork is not persisted. script in fork might be removed in later versions of jPDL");
084: script = new Script();
085: script.read(scriptElement, jpdlReader);
086: }
087: }
088:
089: public void execute(ExecutionContext executionContext) {
090: Token token = executionContext.getToken();
091:
092: // phase one: collect all the transitionNames
093: Collection transitionNames = null;
094: List forkedTokens = new ArrayList();
095:
096: // by default, the fork spawns a token for each leaving transition
097: if (script == null) {
098: transitionNames = getLeavingTransitionsMap().keySet();
099:
100: } else { // a script is specified
101: // if a script is specified, use that script to calculate the set
102: // of leaving transitions to be used for forking tokens.
103: Map outputMap = null;
104: try {
105: outputMap = script.eval(token);
106: } catch (Exception e) {
107: this .raiseException(e, executionContext);
108: }
109: if (outputMap.size() == 1) {
110: Object result = outputMap.values().iterator().next();
111: if (result instanceof Collection) {
112: transitionNames = (Collection) result;
113: }
114: }
115: if (transitionNames == null) {
116: throw new JbpmException(
117: "script for fork '"
118: + name
119: + "' should produce one collection (in one writable variable): "
120: + transitionNames);
121: }
122: }
123:
124: // TODO add some way of blocking the current token here and disable that blocking when the join reactivates this token
125: // Then an exception can be thrown by in case someone tries to signal a token that is waiting in a fork.
126: // Suspend and resume can NOT be used for this since that will also suspend any related timers, tasks and messages...
127: // So a separate kind of blocking should be created for this.
128: // @see also http://jira.jboss.com/jira/browse/JBPM-642
129:
130: // phase two: create forked tokens for the collected transition names
131: Iterator iter = transitionNames.iterator();
132: while (iter.hasNext()) {
133: String transitionName = (String) iter.next();
134: forkedTokens.add(createForkedToken(token, transitionName));
135: }
136:
137: // phase three: launch child tokens from the fork over the given transitions
138: iter = forkedTokens.iterator();
139: while (iter.hasNext()) {
140: ForkedToken forkedToken = (ForkedToken) iter.next();
141: Token childToken = forkedToken.token;
142: String leavingTransitionName = forkedToken.leavingTransitionName;
143: ExecutionContext childExecutionContext = new ExecutionContext(
144: childToken);
145: if (leavingTransitionName != null) {
146: leave(childExecutionContext, leavingTransitionName);
147: } else {
148: leave(childExecutionContext);
149: }
150: }
151: }
152:
153: protected ForkedToken createForkedToken(Token parent,
154: String transitionName) {
155: // instantiate the new token
156: Token childToken = new Token(parent, getTokenName(parent,
157: transitionName));
158:
159: // create a forked token
160: ForkedToken forkedToken = null;
161: forkedToken = new ForkedToken(childToken, transitionName);
162:
163: return forkedToken;
164: }
165:
166: protected String getTokenName(Token parent, String transitionName) {
167: String tokenName = null;
168: if (transitionName != null) {
169: if (!parent.hasChild(transitionName)) {
170: tokenName = transitionName;
171: } else {
172: int i = 2;
173: tokenName = transitionName + Integer.toString(i);
174: while (parent.hasChild(tokenName)) {
175: i++;
176: tokenName = transitionName + Integer.toString(i);
177: }
178: }
179: } else { // no transition name
180: int size = (parent.getChildren() != null ? parent
181: .getChildren().size() + 1 : 1);
182: tokenName = Integer.toString(size);
183: }
184: return tokenName;
185: }
186:
187: public Script getScript() {
188: return script;
189: }
190:
191: public void setScript(Script script) {
192: this .script = script;
193: }
194:
195: static class ForkedToken {
196: Token token = null;
197: String leavingTransitionName = null;
198:
199: public ForkedToken(Token token, String leavingTransitionName) {
200: this .token = token;
201: this .leavingTransitionName = leavingTransitionName;
202: }
203: }
204:
205: private static Log log = LogFactory.getLog(Fork.class);
206: }
|