[jbpm-commits] JBoss JBPM SVN: r3316 - in jbpm3/trunk/modules/core/src: main/java/org/jbpm/db and 3 other directories.

do-not-reply at jboss.org do-not-reply at jboss.org
Wed Dec 10 07:58:43 EST 2008


Author: camunda
Date: 2008-12-10 07:58:42 -0500 (Wed, 10 Dec 2008)
New Revision: 3316

Added:
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractProcessInstanceBaseCommand.java
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractTokenBaseCommand.java
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ResumeProcessInstanceCommand.java
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/SuspendProcessInstanceCommand.java
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/UnlockTokenCommand.java
   jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/
   jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ChangeProcessInstanceVersionCommandTest.java
   jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ProcessInstanceCommandTest.java
   jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/TokenCommandTest.java
Modified:
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/BatchSignalCommand.java
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ChangeProcessInstanceVersionCommand.java
   jbpm3/trunk/modules/core/src/main/java/org/jbpm/db/JobSession.java
   jbpm3/trunk/modules/core/src/main/resources/org/jbpm/db/hibernate.queries.hbm.xml
Log:
JBPM-1905:
- Added Suspend/ResumeProcessInstanceCommand & UnlockTokenCommand (including test cases)
- Introduced Command base class for ProcessInstance / Token related Commands
- Improved / Refactored ChangeProcessInstanceVersionCommand, added TestCase




Added: jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractProcessInstanceBaseCommand.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractProcessInstanceBaseCommand.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractProcessInstanceBaseCommand.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,180 @@
+package org.jbpm.command;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.Query;
+import org.jbpm.JbpmContext;
+import org.jbpm.graph.def.ProcessDefinition;
+import org.jbpm.graph.exe.ProcessInstance;
+
+/**
+ * Abstract base class for all commands working on {@link org.jbpm.graph.exe.ProcessInstance}s.
+ * 
+ * The {@link ProcessInstance} can either be specified by id or multiple ids.
+ * 
+ * The alternative is to specify a {@link ProcessDefinition} name and version.
+ * In this case <b>all</b> found {@link ProcessInstance}s are processed.
+ * If no version is specified, <b>all</b> versions are taken into account.
+ * 
+ *  if onlyRunning is set to false (default is true) already ended {@link ProcessInstance}s
+ *  are processed too.
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public abstract class AbstractProcessInstanceBaseCommand implements Command
+{
+  protected Log log = LogFactory.getLog(this.getClass());
+
+  private long[] processInstanceIds = null;
+  private String processName = null;
+  private int processVersion = 0;
+  private boolean onlyRunning = true;
+
+  private boolean operateOnSingleObject;
+  
+  private transient JbpmContext jbpmContext;
+
+  public AbstractProcessInstanceBaseCommand()
+  {
+    super();
+  }
+  
+  protected JbpmContext getJbpmContext() {
+    return jbpmContext;
+  }
+
+  public Object execute(JbpmContext jbpmContext) throws Exception
+  {
+    this.jbpmContext = jbpmContext;
+    try {      
+      log.debug("executing " + this);
+      
+      // batch tokens
+      if (processInstanceIds != null && processInstanceIds.length > 0)
+      {
+        for (int i = 0; i < processInstanceIds.length; i++)
+        {
+          ProcessInstance pi = jbpmContext.loadProcessInstanceForUpdate(processInstanceIds[i]);
+          execute(pi);
+        }
+      }
+  
+      // search for ProcessInstances according to parameters
+      if (processName != null)
+      {
+        operateOnSingleObject = false;
+        
+        GetProcessInstancesCommand cmd = new GetProcessInstancesCommand();
+        cmd.setProcessName(processName);
+        cmd.setOnlyRunning(onlyRunning);
+        if (processVersion>0)
+          cmd.setVersion(String.valueOf(processVersion));
+        
+        //      Query query = null;
+        //      if (processVersion>0) {
+        //        query = jbpmContext.getSession().getNamedQuery("GraphSession.findProcessDefinitionByNameAndVersion");        
+        //        query.setInteger("version", processVersion);
+        //      }
+        //      else {
+        //        query = jbpmContext.getSession().getNamedQuery("GraphSession.findTokensForProcessInNode");                
+        //      }
+        //      query.setString("name", processName);
+        List processInstanceList = (List)cmd.execute(jbpmContext);
+  
+        Iterator iter = processInstanceList.iterator();
+        while (iter.hasNext())
+        {
+          ProcessInstance pi = (ProcessInstance)iter.next();
+          execute(pi);
+        }
+      }
+  
+      return null;
+    }
+    finally {
+      this.jbpmContext = null;
+    }
+  }
+
+  public abstract void execute(ProcessInstance processInstance);
+  
+  public AbstractProcessInstanceBaseCommand setProcessInstanceIds(long[] processInstanceIds)
+  {
+    this.operateOnSingleObject = false;
+    this.processInstanceIds = processInstanceIds;
+    return this;
+  }
+
+  public AbstractProcessInstanceBaseCommand setProcessInstanceId(long processInstanceId)
+  {
+    this.operateOnSingleObject = true;
+    this.processInstanceIds = new long[1];
+    this.processInstanceIds[0] = processInstanceId;    
+    return this;
+  }
+  
+  public String toString() {
+    if (processName!=null) {
+      return this.getClass().getName() 
+        + " [tokenIds=" + Arrays.toString(processInstanceIds)       
+        + ";processName=" + processName
+        + ";processVersion=" + (processVersion>0 ? processVersion : "NA")
+        + getAdditionalToStringInformation()
+        + "]";
+    }
+    else {
+      return this.getClass().getName() 
+      + " [tokenIds=" + Arrays.toString(processInstanceIds)       
+      + ";operateOnSingleObject=" + operateOnSingleObject
+      + getAdditionalToStringInformation()
+      + "]";      
+    }
+  }  
+
+  public String getAdditionalToStringInformation() {
+    return "";
+  }
+
+  public String getProcessName()
+  {
+    return processName;
+  }
+
+  public AbstractProcessInstanceBaseCommand setProcessName(String processName)
+  {
+    this.processName = processName;
+    return this;
+  }
+  
+  public int getProcessVersion()
+  {
+    return processVersion;
+  }
+
+  public AbstractProcessInstanceBaseCommand setProcessVersion(int processVersion)
+  {
+    this.processVersion = processVersion;
+    return this;
+  }
+
+  public long[] getProcessInstanceIds()
+  {
+    return processInstanceIds;
+  }
+
+  public boolean isOnlyRunning()
+  {
+    return onlyRunning;
+  }
+
+  public AbstractProcessInstanceBaseCommand setOnlyRunning(boolean onlyRunning)
+  {
+    this.onlyRunning = onlyRunning;
+    return this;
+  }
+
+}
\ No newline at end of file

Added: jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractTokenBaseCommand.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractTokenBaseCommand.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/AbstractTokenBaseCommand.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,181 @@
+package org.jbpm.command;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.Query;
+import org.jbpm.JbpmContext;
+import org.jbpm.graph.def.ProcessDefinition;
+import org.jbpm.graph.exe.Token;
+
+/**
+ * Abstract base class for commands working on Tokens.
+ * 
+ * The {@link Token} can either be specified by id or multiple ids.
+ * 
+ * The alternative is to specify a {@link ProcessDefinition} name, a
+ * required node name and version.
+ * In this case <b>all</b> found {@link Token}s are processed.
+ * If no version is specified, <b>all</b> versions are taken into account. 
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public abstract class AbstractTokenBaseCommand implements Command
+{
+  protected Log log = LogFactory.getLog(this.getClass());
+
+  private long[] tokenIds = null;
+  private String processName = null;
+  private String stateName = null;
+  private int processVersion = 0;
+  
+  private boolean operateOnSingleObject;
+
+  private transient JbpmContext jbpmContext;
+
+  public AbstractTokenBaseCommand()
+  {
+    super();
+  }
+  
+  protected JbpmContext getJbpmContext() {
+    return jbpmContext;
+  }
+
+  public Object execute(JbpmContext jbpmContext) throws Exception
+  {
+    this.jbpmContext = jbpmContext;
+    try {          
+      ArrayList result = new ArrayList();
+      log.debug("executing " + this);
+  
+      // batch tokens
+      if (tokenIds != null && tokenIds.length > 0)
+      {
+        for (int i = 0; i < tokenIds.length; i++)
+        {
+          Token token = jbpmContext.loadTokenForUpdate(tokenIds[i]);
+          result.add(
+              execute(token));
+        }
+      }
+  
+      // search for tokens in process/state
+      if (processName != null && stateName != null)
+      {
+        this.operateOnSingleObject = false;
+  
+        Query query = null;
+        if (processVersion>0) {
+          query = jbpmContext.getSession().getNamedQuery("GraphSession.findTokensForProcessVersionInNode");        
+          query.setInteger("processDefinitionVersion", processVersion);
+        }
+        else {
+          query = jbpmContext.getSession().getNamedQuery("GraphSession.findTokensForProcessInNode");                
+        }
+        query.setString("processDefinitionName", processName);
+        query.setString("nodeName", stateName);
+  
+        Iterator iter = query.list().iterator();
+        while (iter.hasNext())
+        {
+          Token token = (Token)iter.next();
+          result.add(
+              execute(token));
+        }
+      }
+  
+      if (operateOnSingleObject) {
+        if (result.size()<1)
+          return null;
+        else 
+          return result.get(0);
+      }
+      else {
+        return result;      
+      }
+    }
+    finally {
+      this.jbpmContext = null;
+    }
+  }
+
+  public abstract Object execute(Token token);
+
+  public AbstractTokenBaseCommand setTokenIds(long[] tokenIds)
+  {
+    this.operateOnSingleObject = false;
+    this.tokenIds = tokenIds;
+    return this;
+  }
+
+  public AbstractTokenBaseCommand setTokenId(long tokenId)
+  {
+    this.operateOnSingleObject = true;
+    this.tokenIds = new long[1];
+    this.tokenIds[0] = tokenId;
+    return this;
+  }
+  
+  public String toString() {
+    if (processName!=null && stateName!=null) {
+      return this.getClass().getName() 
+        + " [tokenIds=" + Arrays.toString(tokenIds)       
+        + ";processName=" + processName
+        + ";processVersion=" + (processVersion>0 ? processVersion : "NA")
+        + ";stateName=" + stateName
+        + getAdditionalToStringInformation()
+        + "]";
+    }
+    else {
+      return this.getClass().getName() 
+      + " [tokenIds=" + Arrays.toString(tokenIds)       
+      + ";operateOnSingleObject=" + operateOnSingleObject
+      + getAdditionalToStringInformation()
+      + "]";      
+    }
+  }
+  
+  public String getAdditionalToStringInformation() {
+    return "";
+  }
+
+  public String getProcessName()
+  {
+    return processName;
+  }
+
+  public void setProcessName(String processName)
+  {
+    this.processName = processName;
+  }
+
+  public int getProcessVersion()
+  {
+    return processVersion;
+  }
+
+  public void setProcessVersion(int processVersion)
+  {
+    this.processVersion = processVersion;
+  }
+
+  public String getStateName()
+  {
+    return stateName;
+  }
+
+  public void setStateName(String stateName)
+  {
+    this.stateName = stateName;
+  }
+
+  public long[] getTokenIds()
+  {
+    return tokenIds;
+  }
+
+}
\ No newline at end of file

Modified: jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/BatchSignalCommand.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/BatchSignalCommand.java	2008-12-10 12:51:17 UTC (rev 3315)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/BatchSignalCommand.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -1,154 +1,62 @@
 package org.jbpm.command;
 
 import java.util.Date;
-import java.util.Iterator;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.Query;
 import org.jbpm.JbpmContext;
 import org.jbpm.graph.exe.Token;
 
 /**
- * a bunch of processes is signalled with this command. you can specify the tokens either <li>by a array of token ids
- * <li>or by processName, processVersion (optional, without all versions), stateName
+ * a bunch of processes is signaled with this command. you can specify the
+ * tokens either
+ * <li>by a array of token ids
+ * <li>or by processName, processVersion (optional, without all versions),
+ * stateName
  * 
- * transitionName speicifies the transition to take (if null, the default transition is taken).
+ * transitionName specifies the transition to take (if null, the default
+ * transition is taken).
  * 
- * This command can be for example useful, if you have a lot of processes to check some information a not jBPM task has
- * altered before (maybe in batch too).
- * 
- * CURRENTLY EXPERIMENTAL!
- * 
- * @author Bernd Rucker (bernd.ruecker at camunda.com)
+ * @author Bernd Ruecker (bernd.ruecker at camunda.com)
  */
-public class BatchSignalCommand implements Command
-{
+public class BatchSignalCommand extends AbstractTokenBaseCommand {
 
-  private static final long serialVersionUID = -4330623193546102772L;
+	private static final long serialVersionUID = -4330623193546102772L;
 
-  private static Log log = LogFactory.getLog(BatchSignalCommand.class);
+	/**
+	 * if set, only tokens which are started after this date are signaled
+	 * (interesting to implement some timeout for example)
+	 */
+	private Date inStateAtLeastSince = null;
 
-  private long[] tokenIds = null;
+	private String transitionName = null;
 
-  private String processName = null;
+	public Object execute(Token token) {
+		if (inStateAtLeastSince == null
+				|| token.getNodeEnter().before(inStateAtLeastSince)) {
 
-  private String stateName = null;
+			log.debug("signal token " + token);
+			if (transitionName == null) {
+				token.signal();
+			} else {
+				token.signal(transitionName);
+			}
+		}
+		return token;
+	}
 
-  /**
-   * if set, only tokens which are started after this date are signaled (interessting to implement some timeout for
-   * example)
-   */
-  private Date inStateAtLeastSince = null;
+	public String getTransitionName() {
+		return transitionName;
+	}
 
-  private long processVersion = 0;
+	public void setTransitionName(String transitionName) {
+		this.transitionName = transitionName;
+	}
 
-  private String transitionName = null;
+	public Date getInStateAtLeastSince() {
+		return inStateAtLeastSince;
+	}
 
-  public Object execute(JbpmContext jbpmContext) throws Exception
-  {
-    log.debug("executing " + this);
+	public void setInStateAtLeastSince(Date inStateAtLeastSince) {
+		this.inStateAtLeastSince = inStateAtLeastSince;
+	}
 
-    // batch tokens
-    if (tokenIds != null && tokenIds.length > 0)
-    {
-      for (int i = 0; i < tokenIds.length; i++)
-      {
-        Token token = jbpmContext.loadTokenForUpdate(tokenIds[i]);
-        signalToken(token);
-      }
-    }
-
-    // search for tokens in process/state
-    if (processName != null && stateName != null)
-    {
-      Query query = jbpmContext.getSession().getNamedQuery("GraphSession.findTokensForProcessInNode");
-      query.setString("processDefinitionName", processName);
-      query.setString("nodeName", stateName);
-
-      Iterator iter = query.list().iterator();
-      while (iter.hasNext())
-      {
-        Token t = (Token)iter.next();
-        if (inStateAtLeastSince == null || t.getNodeEnter().before(inStateAtLeastSince))
-          signalToken(t);
-      }
-    }
-
-    return null;
-  }
-
-  private void signalToken(Token token)
-  {
-    log.debug("signal token " + token);
-    if (transitionName == null)
-    {
-      token.signal();
-    }
-    else
-    {
-      token.signal(transitionName);
-    }
-  }
-
-  public String getProcessName()
-  {
-    return processName;
-  }
-
-  public void setProcessName(String processName)
-  {
-    this.processName = processName;
-  }
-
-  public long getProcessVersion()
-  {
-    return processVersion;
-  }
-
-  public void setProcessVersion(long processVersion)
-  {
-    this.processVersion = processVersion;
-  }
-
-  public String getStateName()
-  {
-    return stateName;
-  }
-
-  public void setStateName(String stateName)
-  {
-    this.stateName = stateName;
-  }
-
-  public long[] getTokenIds()
-  {
-    return tokenIds;
-  }
-
-  public void setTokenIds(long[] tokenIds)
-  {
-    this.tokenIds = tokenIds;
-  }
-
-  public String getTransitionName()
-  {
-    return transitionName;
-  }
-
-  public void setTransitionName(String transitionName)
-  {
-    this.transitionName = transitionName;
-  }
-
-  public Date getInStateAtLeastSince()
-  {
-    return inStateAtLeastSince;
-  }
-
-  public void setInStateAtLeastSince(Date inStateAtLeastSince)
-  {
-    this.inStateAtLeastSince = inStateAtLeastSince;
-  }
-
 }

Modified: jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ChangeProcessInstanceVersionCommand.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ChangeProcessInstanceVersionCommand.java	2008-12-10 12:51:17 UTC (rev 3315)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ChangeProcessInstanceVersionCommand.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -8,66 +8,71 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.hibernate.Query;
-import org.jbpm.JbpmContext;
 import org.jbpm.JbpmException;
-import org.jbpm.db.JbpmSchema;
+import org.jbpm.graph.def.GraphElement;
 import org.jbpm.graph.def.Node;
 import org.jbpm.graph.def.ProcessDefinition;
 import org.jbpm.graph.exe.ProcessInstance;
 import org.jbpm.graph.exe.Token;
+import org.jbpm.graph.node.ProcessState;
+import org.jbpm.graph.node.TaskNode;
+import org.jbpm.job.Job;
+import org.jbpm.job.Timer;
+import org.jbpm.logging.log.ProcessLog;
 import org.jbpm.taskmgmt.def.Task;
 import org.jbpm.taskmgmt.exe.TaskInstance;
 
 /**
- * <b>THIS COMMAND IS NOT YET STABLE, BUT FEEL FREE TO TEST :-)</b>
+ * <b>THIS COMMAND IS NOT YET STABLE, BUT FEEL FREE TO TEST :-)</b><br>
  * 
- * change the version of a running process instance. This works only, if the current node is also available in the new
- * version of the process definition (identified by name, so the name has to be exactly the same). One problem with this
- * approach ist also, that if a task with the same name is moved to another node (but this is a rare case)
+ * Status update: Still not complete, but refactored and added simple test cases: 
+ * {@link ChangeProcessInstanceVersionCommandTest}.<br>
  * 
+ * Change the version of a running process instance. 
  * 
- * make trouble, if there are 2 tokens in the process, because only one actual node is used...
+ * This works only, if the current node is also available in the new
+ * version of the process definition or a name mapping has to be provided.<br> 
  * 
- * Possible workaround: use process id instead of node id.
+ * <b>Currently known limitations:</b>
+ * <ul>
+ *   <li> {@link Task}s cannot move "into" another node. If an active
+ *        {@link TaskInstance} exists, the {@link Task} definition must
+ *        exist in the {@link TaskNode} with the same (or mapped) name.
+ *        Otherwise the right node cannot be found easily because it may be
+ *        ambiguous.
+ *   <li> Sub processes aren't yet tested. Since the {@link ProcessState} is 
+ *        a {@link Node} like any other, it should work anyway.
+ *   <li> Can have <b>negative impact on referential integrity</b>! Because
+ *        one {@link ProcessInstance} can have {@link ProcessLog}s point to
+ *        old {@link ProcessDefinition}s. Hence, delete a {@link ProcessDefinition}
+ *        may not work and throw an Exception (Integrity constraint violation)  
+ * </ul>
  * 
- * TODO: new hibernate query for that? Proposal Fluffi "select distinct task " + "from " + Task.class.getName() +
- * " task " + "where task.name = :taskName " + " and task.processDefinition.id = :processDefinitionId ";
- * 
  * @author Bernd Ruecker (bernd.ruecker at camunda.com)
  */
-public class ChangeProcessInstanceVersionCommand implements Command
+public class ChangeProcessInstanceVersionCommand extends AbstractProcessInstanceBaseCommand
 {
 
   private static final long serialVersionUID = 2277080393930008224L;
 
   /**
-   * process id of process to update. If set, this special process is updated
-   */
-  private long processId = -1;
-
-  /**
-   * if set, all running processes of the process with this name are updated
-   */
-  private String processName;
-
-  /**
    * new version of process, if <=0, the latest process definition is used
    */
   private int newVersion = -1;
 
-  private static final Log log = LogFactory.getLog(JbpmSchema.class);
+  private static final Log log = LogFactory.getLog(ChangeProcessInstanceVersionCommand.class);
 
-  private transient JbpmContext jbpmContext = null;
-
   /**
-   * the map configures for every node-name in the old process definition (as key) which node-name to use in the new
+   * the map configures for every node-name in the old process definition 
+   * (as key) which node-name to use in the new
    * process definition.
    * 
-   * if a node is not mentioned in this Map, old node name = new node name is applied
+   * if a node is not mentioned in this Map, 
+   * old node name = new node name is applied
    */
-  private Map nameMapping = new HashMap();
+  private Map<String, String> nodeNameMapping = new HashMap<String, String>();
 
-  private transient ProcessDefinition newDef;
+  private Map<String, String> taskNameMapping = new HashMap<String, String>();
 
   public ChangeProcessInstanceVersionCommand()
   {
@@ -75,133 +80,165 @@
 
   public ChangeProcessInstanceVersionCommand(long processId, int newVersion)
   {
-    this.processId = processId;
+    super();
+    super.setProcessInstanceId(processId);
     this.newVersion = newVersion;
   }
-
-  private ProcessDefinition getNewDef(String processName)
-  {
-    if (newDef == null)
-    {
-      if (newVersion <= 0)
-        newDef = jbpmContext.getGraphSession().findLatestProcessDefinition(processName);
-      else
-        newDef = jbpmContext.getGraphSession().findProcessDefinition(processName, newVersion);
-    }
-    return newDef;
+  
+  public String getAdditionalToStringInformation() {
+    return ";newVersion=" + newVersion;
   }
 
-  /**
-   * @return always null
-   * @see org.jbpm.command.Command#execute(org.jbpm.JbpmContext)
-   */
-  public Object execute(JbpmContext jbpmContext) throws Exception
+  private ProcessDefinition loadNewProcessDefinition(String processName)
   {
-    this.jbpmContext = jbpmContext;
-    if (processId > -1)
-    {
-      ProcessInstance pi = jbpmContext.getGraphSession().loadProcessInstance(processId);
-      changeProcessVersion(pi);
-    }
-    if (processName != null && processName.length() > 0)
-    {
-      changeAllProcessInstances(processName);
-    }
-    return null;
+    if (newVersion <= 0)
+      return getJbpmContext().getGraphSession().findLatestProcessDefinition(processName);
+    else
+      return getJbpmContext().getGraphSession().findProcessDefinition(processName, newVersion);
   }
 
-  private void changeProcessVersion(ProcessInstance pi)
+  public void execute(ProcessInstance pi)
   {
-    changeTokenVersion(jbpmContext, pi.getRootToken());
-
     ProcessDefinition oldDef = pi.getProcessDefinition();
-    ProcessDefinition newDef = getNewDef(oldDef.getName());
+    ProcessDefinition newDef = loadNewProcessDefinition(oldDef.getName());
 
-    log.debug("changes process id " + pi.getId() + " from version " + pi.getProcessDefinition().getVersion() + " to new version " + newDef.getVersion());
-
+    log.debug("Start changing process id " + pi.getId() + " from version " + pi.getProcessDefinition().getVersion() + " to new version " + newDef.getVersion());
     pi.setProcessDefinition(newDef);
 
+    changeTokenVersion(pi.getRootToken());
+
     log.debug("process id " + pi.getId() + " changed to version " + pi.getProcessDefinition().getVersion());
   }
+  
+  private ProcessDefinition getNewProcessDefinition(Token t)  {
+    return t.getProcessInstance().getProcessDefinition();
+  }
 
-  private void changeAllProcessInstances(String processName) throws Exception
-  {
-    log.debug("changing version all processes '" + processName + "'");
+  private void changeTokenVersion(Token token)
+  {    
+    ProcessDefinition newDef = getNewProcessDefinition(token);
+    log.debug("change token id " + token.getId() + " to new version " + newDef.getVersion());
 
-    GetProcessInstancesCommand cmd = new GetProcessInstancesCommand();
-    cmd.setProcessName(processName);
-    cmd.setOnlyRunning(true);
-
-    List instances = (List)cmd.execute(jbpmContext);
-    for (Iterator iter = instances.iterator(); iter.hasNext();)
-    {
-      ProcessInstance pi = (ProcessInstance)iter.next();
-      changeProcessVersion(pi);
-    }
-  }
-
-  private void changeTokenVersion(JbpmContext jbpmContext, Token token)
-  {
+    // change node reference on token (current node)
     Node oldNode = token.getNode();
+    Node newNode = findReplacementNode(newDef, oldNode);
+    token.setNode(newNode);
 
-    ProcessDefinition oldDef = token.getProcessInstance().getProcessDefinition();
-    ProcessDefinition newDef = getNewDef(oldDef.getName());
+    // Change timers too!
+    adjustTimersForToken(token);
 
-    Node newNode = newDef.findNode(getNewNodeName(oldNode));
+    // change tasks
+    adjustTaskInstancesForToken(token);
 
-    if (newNode == null)
-    {
-      throw new JbpmException("node with name '" + getNewNodeName(oldNode) + "' not found in new process definition");
+    // change children recursively
+    if (token.getChildren()!=null) {
+      Iterator<Token> tokenIter = token.getChildren().values().iterator();
+      while (tokenIter.hasNext())
+      {
+        changeTokenVersion(tokenIter.next());
+      }
     }
+  }
 
-    log.debug("change token id " + token.getId() + " from version " + oldDef.getVersion() + " to new version " + newDef.getVersion());
-
-    token.setNode(newNode);
-
-    // TODO: Change timers too!
-
-    // change tasks
-    Iterator iter = getTasksForToken(token).iterator();
+  private void adjustTaskInstancesForToken(Token token)
+  {
+    ProcessDefinition newDef = getNewProcessDefinition(token);
+    Iterator<TaskInstance> iter = getTasksForToken(token).iterator();
     while (iter.hasNext())
     {
-      TaskInstance ti = (TaskInstance)iter.next();
+      TaskInstance ti = iter.next();
 
+      // find new task
       Task oldTask = ti.getTask();
-      // find new task
-      Query q = jbpmContext.getSession().getNamedQuery("TaskMgmtSession.findTaskForNode");
-      q.setString("taskName", oldTask.getName());
-      q.setLong("taskNodeId", newNode.getId());
-      // TODO: q.setLong("processDefinitionId", newDef.getId());
+      Node oldNode = oldTask.getTaskNode();
+      
+      Task newTask = findReplacementTask(newDef, oldNode, oldTask);
+      ti.setTask(newTask);
+      log.debug("change dependent task-instance with id " + oldTask.getId());
+    }
+  }
 
-      Task newTask = (Task)q.uniqueResult();
-
-      if (newTask == null)
+  private void adjustTimersForToken(Token token)
+  {
+    ProcessDefinition newDef = getNewProcessDefinition(token);
+    List<Job> jobs = getJbpmContext().getJobSession().findJobsByToken(token);
+    for (Job job : jobs)
+    {
+      if (job instanceof Timer) 
       {
-        throw new JbpmException("node '" + newNode.getName() + "' has no Task configured! Check the new process definition");
+        // check all timers if connected to a GraphElement
+        Timer timer = (Timer)job;
+        if (timer.getGraphElement()!=null) {
+          
+          // and change the reference (take name mappings into account!)
+          if (timer.getGraphElement() instanceof Task) {
+            // change to new task definition
+            Task oldTask = (Task)timer.getGraphElement();
+            TaskNode oldNode =  oldTask.getTaskNode();
+            timer.setGraphElement( 
+                findReplacementTask(newDef, oldNode, oldTask));
+          }
+          else {
+            // change to new node
+            GraphElement oldNode = timer.getGraphElement();
+            // TODO: What with other GraphElements?
+            timer.setGraphElement( 
+                findReplacementNode(newDef, oldNode));
+          }          
+        }
       }
-
-      ti.setTask(newTask);
-      log.debug("change dependent task-instance with id " + oldTask.getId());
     }
+  }
+  
+  private Node findReplacementNode(ProcessDefinition newDef, GraphElement oldNode)
+  {
+    String name = getReplacementNodeName( oldNode );
+    Node newNode = newDef.findNode(name);
+    if (newNode == null)
+    {
+      throw new JbpmException("node with name '" + name + "' not found in new process definition");
+    }
+    return newNode;
+  }
 
-    // change childs recursive
-    Iterator childIter = token.getChildren().values().iterator();
-    while (childIter.hasNext())
+  private Task findReplacementTask(ProcessDefinition newDef, Node oldNode, Task oldTask)
+  {
+    String replacementTaskName = getReplacementTaskName( oldTask );    
+    Node newTaskNode = findReplacementNode(newDef, oldNode);
+    
+    Query q = getJbpmContext().getSession().getNamedQuery("TaskMgmtSession.findTaskForNode");
+    q.setString("taskName", replacementTaskName);
+    q.setLong("taskNodeId", newTaskNode.getId());
+
+    Task newTask = (Task)q.uniqueResult();    
+    if (newTask == null)
     {
-      changeTokenVersion(jbpmContext, (Token)childIter.next());
+      throw new JbpmException("Task '" + replacementTaskName + "' for node '" + newTaskNode.getName() + "' not found in new process definition");
     }
+    return newTask;
   }
 
   /**
    * @param oldNode
    * @return the name of the new node (given in the map or return default value, which is the old node name)
    */
-  private String getNewNodeName(Node oldNode)
+  private String getReplacementNodeName(GraphElement oldNode)
+  {    
+    String oldName = (oldNode instanceof Node ? ((Node)oldNode).getFullyQualifiedName() : oldNode.getName());
+    if (nodeNameMapping.containsKey(oldName))
+    {
+      return (String)nodeNameMapping.get(oldName);
+    }
+    // return new node name = old node name as default
+    return oldName;
+  }
+
+  private String getReplacementTaskName(Task oldTask)
   {
-    String oldName = oldNode.getFullyQualifiedName();
-    if (nameMapping.containsKey(oldName))
+    String oldName = oldTask.getName();
+    if (taskNameMapping.containsKey(oldName))
     {
-      return (String)nameMapping.get(oldName);
+      return (String)taskNameMapping.get(oldName);
     }
     // return new node name = old node name as default
     return oldName;
@@ -214,23 +251,25 @@
    */
   private List getTasksForToken(Token token)
   {
-    Query query = jbpmContext.getSession().getNamedQuery("TaskMgmtSession.findTaskInstancesByTokenId");
+    Query query = getJbpmContext().getSession().getNamedQuery("TaskMgmtSession.findTaskInstancesByTokenId");
     query.setLong("tokenId", token.getId());
     return query.list();
 
   }
 
-  public Map getNameMapping()
+  public Map getNodeNameMapping()
   {
-    return nameMapping;
+    return nodeNameMapping;
   }
-
-  public void setNameMapping(Map nameMapping)
+ 
+  public ChangeProcessInstanceVersionCommand setNodeNameMapping(Map<String, String> nameMapping)
   {
-    if (nameMapping == null)
-      this.nameMapping = new HashMap();
-    else
-      this.nameMapping = nameMapping;
+    if (nameMapping == null) {
+      this.nodeNameMapping = new HashMap<String, String>();      
+    } else {
+      this.nodeNameMapping = nameMapping;
+    }
+    return this;
   }
 
   public int getNewVersion()
@@ -238,29 +277,60 @@
     return newVersion;
   }
 
-  public void setNewVersion(int newVersion)
+  public ChangeProcessInstanceVersionCommand setNewVersion(int newVersion)
   {
     this.newVersion = newVersion;
+    return this;
   }
+  
+  public Map getTaskNameMapping()
+  {
+    return taskNameMapping;
+  }
 
+  public ChangeProcessInstanceVersionCommand setTaskNameMapping(Map<String, String> nameMapping)
+  {
+    if (nameMapping == null) {
+      this.taskNameMapping = new HashMap<String, String>();      
+    } else {
+      this.taskNameMapping = nameMapping;
+    }
+    return this;
+  }
+
+  /**
+   * @deprecated use getProcessInstanceId instead
+   */
   public long getProcessId()
   {
-    return processId;
+    if (getProcessInstanceIds()!=null && getProcessInstanceIds().length>0)
+      return getProcessInstanceIds()[0];
+    else
+      return 0;
   }
 
+  /**
+   * @deprecated use setProcessInstanceId instead
+   */  
   public void setProcessId(long processId)
   {
-    this.processId = processId;
+    super.setProcessInstanceId(processId);
   }
 
-  public String getProcessName()
+  /**
+   * @deprecated use getNodeNameMapping instead
+   */
+  public Map getNameMapping()
   {
-    return processName;
+    return getNodeNameMapping();
   }
 
-  public void setProcessName(String processName)
-  {
-    this.processName = processName;
+  /**
+   * @deprecated use setNodeNameMapping instead
+   */
+  public ChangeProcessInstanceVersionCommand setNameMapping(Map nameMapping) {
+    return setNodeNameMapping(nameMapping);
   }
 
+
 }

Added: jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ResumeProcessInstanceCommand.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ResumeProcessInstanceCommand.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/ResumeProcessInstanceCommand.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,19 @@
+package org.jbpm.command;
+
+import org.jbpm.JbpmContext;
+import org.jbpm.graph.exe.ProcessInstance;
+
+/**
+ * Resume the specified {@link ProcessInstance}(s). See {@link AbstractProcessInstanceBaseCommand}
+ * to check possibilities to specify {@link ProcessInstance}(s).  
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public class ResumeProcessInstanceCommand extends AbstractProcessInstanceBaseCommand {
+	
+  @Override
+  public void execute(ProcessInstance processInstance)
+  {
+    processInstance.resume();    
+  }
+}

Added: jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/SuspendProcessInstanceCommand.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/SuspendProcessInstanceCommand.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/SuspendProcessInstanceCommand.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,24 @@
+package org.jbpm.command;
+
+import org.jbpm.JbpmContext;
+import org.jbpm.graph.def.ProcessDefinition;
+import org.jbpm.graph.exe.ProcessInstance;
+
+/**
+ * Suspend the specified {@link ProcessInstance}(s). See {@link AbstractProcessInstanceBaseCommand}
+ * to check possibilities to specify {@link ProcessInstance}(s).
+ * 
+ * With filter to all {@link ProcessDefinition}s this can be used
+ * like an emergency shutdown for {@link ProcessDefinition}s. 
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public class SuspendProcessInstanceCommand extends AbstractProcessInstanceBaseCommand {
+
+  @Override
+  public void execute(ProcessInstance processInstance)
+  {
+    processInstance.suspend();
+  }
+
+}

Added: jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/UnlockTokenCommand.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/UnlockTokenCommand.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/command/UnlockTokenCommand.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,48 @@
+package org.jbpm.command;
+
+import org.jbpm.JbpmContext;
+import org.jbpm.graph.exe.Token;
+
+/**
+ * Unlocks the given token. Either the correct lockOwner has 
+ * to be provided (otherwise an exception is thrown) or
+ * NO lockOwner is provided at all, then the lock is 
+ * removed "with force". 
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public class UnlockTokenCommand extends AbstractTokenBaseCommand {
+	
+	private static final long serialVersionUID = 1L;
+	
+	private String lockOwner;
+
+	public UnlockTokenCommand() {		
+	}
+
+  public Object execute(Token token)
+  {
+		if (lockOwner!=null) {
+			token.unlock(lockOwner);
+		}
+		else {
+			// requires newer jbpm version, see https://jira.jboss.org/jira/browse/JBPM-1888
+			token.foreUnlock();
+		}
+		return token;
+	}
+	
+	public String getAdditionalToStringInformation() {
+		return (lockOwner!=null ? ";lockOwner=" + lockOwner : ""); 
+	}
+	
+	public String getLockOwner() {
+		return lockOwner;
+	}
+
+	public UnlockTokenCommand setLockOwner(String lockOwner) {
+		this.lockOwner = lockOwner;
+    return this;
+	}
+
+}

Modified: jbpm3/trunk/modules/core/src/main/java/org/jbpm/db/JobSession.java
===================================================================
--- jbpm3/trunk/modules/core/src/main/java/org/jbpm/db/JobSession.java	2008-12-10 12:51:17 UTC (rev 3315)
+++ jbpm3/trunk/modules/core/src/main/java/org/jbpm/db/JobSession.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -76,6 +76,20 @@
     }
     return jobs;
   }
+  
+  /**
+   * find all jobs
+   */
+  public List<Job> findJobsByToken(Token token) {
+    try {
+      Query query = session.getNamedQuery("JobSession.findJobsByToken");
+      query.setParameter("token", token);
+      List<Job> jobs = query.list();
+      return jobs;
+    } catch (Exception e) {
+      throw new JbpmException("couldn't find jobs for token '"+token+"'", e);
+    }
+  }
 
   public Job getFirstDueJob(String lockOwner, Collection jobIdsToIgnore) {
     Job job = null;

Modified: jbpm3/trunk/modules/core/src/main/resources/org/jbpm/db/hibernate.queries.hbm.xml
===================================================================
--- jbpm3/trunk/modules/core/src/main/resources/org/jbpm/db/hibernate.queries.hbm.xml	2008-12-10 12:51:17 UTC (rev 3315)
+++ jbpm3/trunk/modules/core/src/main/resources/org/jbpm/db/hibernate.queries.hbm.xml	2008-12-10 12:58:42 UTC (rev 3316)
@@ -62,7 +62,7 @@
       select pd
       from org.jbpm.graph.def.ProcessDefinition as pd
       where pd.name = :name
-      order by pd.version desC
+      order by pd.version desc
     ]]>
   </query>
   
@@ -141,6 +141,16 @@
       and token.node.name = :nodeName
     ]]>
   </query>
+
+  <query name="GraphSession.findTokensForProcessVersionInNode">
+    <![CDATA[
+      select token
+      from org.jbpm.graph.exe.Token token
+      where token.processInstance.processDefinition.name = :processDefinitionName
+      and token.processInstance.processDefinition.version = :processDefinitionVersion
+      and token.node.name = :nodeName
+    ]]>
+  </query>
   
   <query name="GraphSession.findProcessInstanceByKey">
     <![CDATA[
@@ -329,6 +339,14 @@
         and job.lockOwner is null
     ]]>
   </query>
+  
+  <query name="JobSession.findJobsByToken">
+    <![CDATA[
+      select job
+      from org.jbpm.job.Job as job
+      where job.token = :token
+    ]]>
+  </query>
 
   <!-- related to Tasks            -->
   <!-- ########################### -->

Added: jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ChangeProcessInstanceVersionCommandTest.java
===================================================================
--- jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ChangeProcessInstanceVersionCommandTest.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ChangeProcessInstanceVersionCommandTest.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,643 @@
+package org.jbpm.command;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.jbpm.JbpmException;
+import org.jbpm.db.AbstractDbTestCase;
+import org.jbpm.graph.def.ProcessDefinition;
+import org.jbpm.graph.exe.ProcessInstance;
+import org.jbpm.graph.exe.Token;
+import org.jbpm.job.Job;
+import org.jbpm.job.Timer;
+import org.jbpm.taskmgmt.exe.TaskInstance;
+
+/**
+ * Tests for {@link ChangeProcessInstanceVersionCommand}
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public class ChangeProcessInstanceVersionCommandTest extends AbstractDbTestCase
+{
+  
+  private ProcessDefinition pd1;
+  private ProcessDefinition pd2;
+  
+  @Override
+  protected void tearDown() throws Exception
+  {
+    if (pd1!=null && pd2!=null) {
+      newTransaction();
+      // IMPORTANT: The ProcessDefinitions have to be deleted in one transaction,
+      // in the right order (new definition with ProcessInstance first) or the
+      // ProcessInstance has to be deleted first independently.
+      
+      // This is because Logs of the ProcessInstance point to both ProcessDefinitions 
+      // (the old and the new one) but only with the new ProcessDefinition the 
+      // ProcessInstance is deleted
+      
+      // exceptions look like this: could not delete: [org.jbpm.graph.def.Transition#9]
+      // Integrity constraint violation FK_LOG_TRANSITION table: JBPM_LOG in statement [/* delete org.jbpm.graph.def.Transition */ delete from JBPM_TRANSITION where ID_=?]
+      // or hibernate batch failed
+
+      // IMPORTANT: Keep this order of deletions! Otherwise if there is
+      // more than one ProcessInstance for the ProcessDefinition a HibernateSeassion.flush
+      // is called when queriing the seconf ProcessInstance after deleting the first
+      // one which may fire a Integrity constraint violation (same problem as described
+      // above), in this case I got
+      // could not delete: [org.jbpm.taskmgmt.def.TaskMgmtDefinition#2]
+      // Integrity constraint violation FK_TASKMGTINST_TMD table: JBPM_MODULEINSTANCE in statement [/* delete org.jbpm.taskmgmt.def.TaskMgmtDefinition */ delete from JBPM_MODULEDEFINITION where ID_=?]
+      jbpmContext.getGraphSession().deleteProcessDefinition(pd2.getId());
+      jbpmContext.getGraphSession().deleteProcessDefinition(pd1.getId());
+    } else if (pd1!=null) {
+      newTransaction();
+      jbpmContext.getGraphSession().deleteProcessDefinition(pd1.getId());
+    } else if (pd2!=null) {
+      newTransaction();
+      jbpmContext.getGraphSession().deleteProcessDefinition(pd2.getId());      
+    }
+    
+    pd1=null;
+    pd2=null;    
+    super.tearDown();
+  }
+
+  /**
+   * test easy version migration (no fork or other stuff)
+   * but with name mapping (different state name in new process
+   * definition)
+   */
+  public void testNameMapping() throws Exception
+  {
+    String xmlVersion1 = //
+    "<process-definition name='TestChangeVersion'>" //
+        + "   <start-state name='start'>" //
+        + "      <transition name='state1' to='state1' />"//
+        + "      <transition name='state2' to='state2' />"//
+        + "   </start-state>" //
+        + "   <state name='state1'>" //
+        + "      <transition name='end1' to='end' />" //
+        + "   </state>" //
+        + "   <state name='state2'>" //
+        + "      <transition name='end2' to='end' />" //
+        + "   </state>" //
+        + "   <end-state name='end'/>" //
+        + "</process-definition>";
+
+    pd1 = ProcessDefinition.parseXmlString(xmlVersion1);
+    jbpmContext.deployProcessDefinition(pd1);
+    pd1 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // start 2 instances
+    ProcessInstance pi1 = jbpmContext.newProcessInstance("TestChangeVersion");
+    pi1.signal("state1");
+    ProcessInstance pi2 = jbpmContext.newProcessInstance("TestChangeVersion");
+    pi2.signal("state2");
+
+    String xmlVersion2 = //
+    "<process-definition name='TestChangeVersion'>" //
+        + "   <start-state name='start'>" //
+        + "      <transition name='state1' to='state1' />"//
+        + "      <transition name='state2' to='state2b'/>"//
+        + "   </start-state>" //
+        + "   <state name='state1'>" //
+        + "      <transition name='end1' to='end' />" //
+        + "   </state>" //
+        + "   <state name='state2b'>" //
+        + "      <transition name='end2b' to='end' />" //
+        + "   </state>" //
+        + "   <end-state name='end' />" //
+        + "</process-definition>";
+
+    pd2 = ProcessDefinition.parseXmlString(xmlVersion2);
+    jbpmContext.deployProcessDefinition(pd2);
+    pd2 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // now change all process instances to most current version
+    try
+    {
+      new ChangeProcessInstanceVersionCommand().setProcessName("TestChangeVersion").execute(jbpmContext);
+      fail("Exception expected, saying that state2 is missing in new version");
+    }
+    catch (JbpmException ex)
+    {
+      assertEquals("node with name 'state2' not found in new process definition", ex.getMessage());
+    }
+
+    HashMap nameMap = new HashMap();
+    nameMap.put("state2", "state2b");
+    // now supply a mapping for the missing node
+    new ChangeProcessInstanceVersionCommand().setNodeNameMapping(nameMap).setProcessName("TestChangeVersion").execute(jbpmContext);
+
+    newTransaction();
+    pi1 = graphSession.loadProcessInstance(pi1.getId());
+    pi2 = graphSession.loadProcessInstance(pi2.getId());
+
+    assertEquals("state1", pi1.getRootToken().getNode().getName());
+    assertEquals(pd2.getNode("state1").getId(), pi1.getRootToken().getNode().getId());
+
+    assertEquals("state2b", pi2.getRootToken().getNode().getName());
+    assertEquals(pd2.getNode("state2b").getId(), pi2.getRootToken().getNode().getId());
+
+    pi1.getRootToken().signal("end1");
+    pi2.getRootToken().signal("end2b");
+
+    newTransaction();
+    pi1 = graphSession.loadProcessInstance(pi1.getId());
+    pi2 = graphSession.loadProcessInstance(pi2.getId());
+
+    assertEquals(pd2.getNode("end").getId(), pi1.getRootToken().getNode().getId());
+    assertTrue(pi1.hasEnded());
+    assertEquals(pd2.getNode("end").getId(), pi2.getRootToken().getNode().getId());
+    assertTrue(pi2.hasEnded());    
+  }
+
+  /**
+   * check that update of nodes work correctly if a fork was involved
+   * and multiple child tokens exist
+   */
+  public void testSubTokensInFork() throws Exception
+  {
+    String xmlVersion1 = //
+    "<process-definition name='TestChangeVersion'>" //
+        + "   <start-state name='start'>" //
+        + "      <transition to='fork' />" //
+        + "   </start-state>" //
+        + "   <fork name='fork'>" //
+        + "      <transition name='path1' to='path1' />" //
+        + "      <transition name='path2' to='path2' />" //
+        + "   </fork>" //
+        + "   <state name='path1'>" //
+        + "      <transition to='join' />" //
+        + "   </state>" //
+        + "   <state name='path2'>" //
+        + "      <transition to='join' />" //
+        + "   </state>" //
+        + "   <join name='join'>" //
+        + "      <transition to='end' />" //
+        + "   </join>" //
+        + "   <end-state name='end' />" //
+        + "</process-definition>";
+
+    jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion1));
+    pd1 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // start instance
+    ProcessInstance pi1 = jbpmContext.newProcessInstance("TestChangeVersion");
+    pi1.signal();
+    Token t1 = pi1.getRootToken().getChild("path1");
+    Token t2 = pi1.getRootToken().getChild("path2");
+
+    String xmlVersion2 = //
+    "<process-definition name='TestChangeVersion'>" //
+        + "   <start-state name='start'>" //
+        + "      <transition to='fork' />" //
+        + "   </start-state>" //
+        + "   <fork name='fork'>" //
+        + "      <transition name='path1' to='path1' />" //
+        + "      <transition name='path2' to='path2b' />" //
+        + "   </fork>" //
+        + "   <state name='path1'>" //
+        + "      <transition to='join' />" //
+        + "   </state>" //
+        + "   <state name='path2b'>" //
+        + "      <transition name='2b' to='join' />" //
+        + "   </state>" //
+        + "   <join name='join'>" //
+        + "      <transition to='end' />" //
+        + "   </join>" //
+        + "   <end-state name='end' />" //
+        + "</process-definition>";
+
+    jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion2));
+    pd2 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // now change all process instances to most current version
+    try
+    {
+      new ChangeProcessInstanceVersionCommand().setProcessInstanceId(pi1.getId()).execute(jbpmContext);
+      fail("Exception expected, saying that phase2 is missing in new version");
+    }
+    catch (JbpmException ex)
+    {
+      assertEquals("node with name 'path2' not found in new process definition", ex.getMessage());
+    }
+
+    HashMap nameMap = new HashMap();
+    nameMap.put("path2", "path2b");
+    // now supply a mapping for the missing node
+    new ChangeProcessInstanceVersionCommand().setNodeNameMapping(nameMap).setProcessInstanceId(pi1.getId()).execute(jbpmContext);
+
+    newTransaction();
+
+    t1 = graphSession.getToken(t1.getId());
+    t2 = graphSession.getToken(t2.getId());
+
+    assertEquals(pd2.getNode("path1").getId(), t1.getNode().getId());
+    assertEquals(pd2.getNode("path2b").getId(), t2.getNode().getId());
+
+    t1.signal();
+    t2.signal("2b");
+
+    pi1 = graphSession.getProcessInstance(pi1.getId());
+
+    assertEquals(pd2.getNode("end").getId(), pi1.getRootToken().getNode().getId());
+    assertTrue(pi1.hasEnded());
+  }
+  
+  public void testTaskInFork() throws Exception
+  {
+    String xmlVersion1 = //
+      "<process-definition name='TestChangeVersion'>" //
+          + "   <start-state name='start'>" //
+          + "      <transition to='fork' />" //
+          + "   </start-state>" //
+          + "   <fork name='fork'>" //
+          + "      <transition name='path1' to='task1' />" //
+          + "      <transition name='path2' to='task2' />" //
+          + "   </fork>" //
+          + "   <task-node name='task1'>" //
+          + "      <task name='theTask1' />" //
+          + "      <transition to='join' />" //
+          + "   </task-node>" //
+          + "   <task-node name='task2'>" //
+          + "      <task name='theTask2' />" //
+          + "      <transition to='join' />" //
+          + "   </task-node>" //
+          + "   <join name='join'>" //
+          + "      <transition to='end' />" //
+          + "   </join>" //
+          + "   <end-state name='end' />" //
+          + "</process-definition>";
+
+      jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion1));
+      pd1 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+      // start instance
+      ProcessInstance pi1 = jbpmContext.newProcessInstance("TestChangeVersion");
+      pi1.signal();
+      Token t1 = pi1.getRootToken().getChild("path1");
+      Token t2 = pi1.getRootToken().getChild("path2");
+
+      String xmlVersion2 = //
+        "<process-definition name='TestChangeVersion'>" //
+        + "   <start-state name='start'>" //
+        + "      <transition to='fork' />" //
+        + "   </start-state>" //
+        + "   <fork name='fork'>" //
+        + "      <transition name='path1' to='task1' />" //
+        + "      <transition name='path2' to='task2' />" //
+        + "   </fork>" //
+        + "   <task-node name='task1b'>" //
+        + "      <task name='theTask1b' />" //
+        + "      <transition to='join' />" //
+        + "   </task-node>" //
+        + "   <task-node name='task2b'>" //
+        + "      <task name='theTask2b' />" //
+        + "      <transition to='join' />" //
+        + "   </task-node>" //
+        + "   <join name='join'>" //
+        + "      <transition to='end' />" //
+        + "   </join>" //
+        + "   <end-state name='end' />" //
+        + "</process-definition>";
+
+      jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion2));
+      pd2 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+      HashMap<String, String> nodeNameMap = new HashMap<String, String>();
+      nodeNameMap.put("task1", "task1b");
+      nodeNameMap.put("task2", "task2b");
+      HashMap<String, String> taskNameMap = new HashMap<String, String>();
+      taskNameMap.put("theTask1", "theTask1b");
+      taskNameMap.put("theTask2", "theTask2b");
+
+      // now supply a mapping for the missing node
+      new ChangeProcessInstanceVersionCommand()
+        .setNodeNameMapping(nodeNameMap)
+        .setTaskNameMapping(taskNameMap)
+        .setProcessInstanceId(pi1.getId())
+        .execute(jbpmContext);
+
+      newTransaction();
+
+      t1 = jbpmContext.loadTokenForUpdate(t1.getId());
+      assertEquals(pd2.getNode("task1b").getId(), t1.getNode().getId());
+      
+      Iterator<TaskInstance> taskInstanceIter = t1.getProcessInstance().getTaskMgmtInstance().getTaskInstances().iterator();
+      TaskInstance ti1 = taskInstanceIter.next();
+      if ("theTask2b".equals(ti1.getTask().getName())) {
+        // this was the wrong one
+        ti1 = taskInstanceIter.next();
+      }
+      assertEquals("theTask1b", ti1.getTask().getName());
+      assertEquals(pd2.getTaskMgmtDefinition().getTask("theTask1b").getId(), ti1.getTask().getId());
+
+      ti1.end();
+      
+      ///////
+      newTransaction();
+      
+      t2 = graphSession.getToken(t2.getId());
+      assertEquals(pd2.getNode("task2b").getId(), t2.getNode().getId());
+
+      taskInstanceIter = t2.getProcessInstance().getTaskMgmtInstance().getTaskInstances().iterator();
+      TaskInstance ti2 = taskInstanceIter.next();
+      if ("theTask1b".equals(ti2.getTask().getName())) {
+        // this was the wrong one
+        ti2 = taskInstanceIter.next();
+      }
+      assertEquals("theTask2b", ti2.getTask().getName());
+      assertEquals(pd2.getTaskMgmtDefinition().getTask("theTask2b").getId(), ti2.getTask().getId());
+
+      ti2.end();
+      
+      newTransaction();
+      pi1 = graphSession.loadProcessInstance(pi1.getId());
+
+      assertEquals(pd2.getNode("end").getId(), pi1.getRootToken().getNode().getId());
+      assertTrue(pi1.hasEnded());    
+      closeJbpmContext();
+  }
+
+  public void testSubProcesses()
+  {
+
+  }
+
+  /**
+   * check, that TaskInstances work (TaskInstance reference to Task has
+   * to be adjusted as will)
+   */
+  public void testTaskInstances() throws Exception
+  {
+    String xmlVersion1 = //
+    "<process-definition name='testTaskInstances'>" //
+        + "   <start-state name='start'>" //
+        + "      <transition name='path1' to='task1' />"//
+        + "      <transition name='path2' to='task2' />"//
+        + "   </start-state>" //
+        + "   <task-node name='task1'>" //
+        + "      <task name='theTask1'/>" //
+        + "      <transition name='end1' to='end' />" //
+        + "   </task-node>" //
+        + "   <task-node name='task2'>" //
+        + "      <task name='theTask2'/>" //
+        + "      <transition name='end2' to='end' />" //
+        + "   </task-node>" //
+        + "   <end-state name='end'/>" //
+        + "</process-definition>";
+
+    pd1 = ProcessDefinition.parseXmlString(xmlVersion1);
+    jbpmContext.deployProcessDefinition(pd1);
+    pd1 = jbpmContext.getGraphSession().findLatestProcessDefinition("testTaskInstances");
+
+    // start 2 instances
+    ProcessInstance pi1 = jbpmContext.newProcessInstance("testTaskInstances");
+    pi1.signal("path1");
+    ProcessInstance pi2 = jbpmContext.newProcessInstance("testTaskInstances");
+    pi2.signal("path2");
+
+    String xmlVersion2 = //
+    "<process-definition name='testTaskInstances'>" //
+        + "   <start-state name='start'>" //
+        + "      <transition name='path1' to='task1' />"//
+        + "      <transition name='path2' to='task2b' />"//
+        + "   </start-state>" //
+        + "   <task-node name='task1'>" //
+        + "      <task name='theTask1'/>" //
+        + "      <transition name='end1' to='end' />" //
+        + "   </task-node>" //
+        + "   <task-node name='task2b'>" //
+        + "      <task name='theTask2b'/>" //
+        + "      <transition name='end2b' to='end' />" //
+        + "   </task-node>" //
+        + "   <end-state name='end'/>" //
+        + "</process-definition>";
+
+    pd2 = ProcessDefinition.parseXmlString(xmlVersion2);
+    jbpmContext.deployProcessDefinition(pd2);
+    pd2 = jbpmContext.getGraphSession().findLatestProcessDefinition("testTaskInstances");
+
+    // process instance 1 can me updated, state names haven't changed in this path:
+    new ChangeProcessInstanceVersionCommand()
+      .setProcessInstanceId(pi1.getId())
+      .execute(jbpmContext);
+
+    HashMap nameMap = new HashMap();
+    nameMap.put("task2", "task2b");
+
+    // now change all process instances to most current version
+    try
+    {
+      new ChangeProcessInstanceVersionCommand()
+        .setNodeNameMapping(nameMap)
+        .setTaskNameMapping(nameMap)
+        .setProcessName("testTaskInstances")
+        .execute(jbpmContext);
+      // fail because task2 is not mapped
+      fail("Exception expected, saying that theTask2b is missing in new version");
+    }
+    catch (JbpmException ex)
+    {
+      assertEquals("Task 'theTask2' for node 'task2b' not found in new process definition", ex.getMessage());
+    }
+
+    // now supply a mapping for the missing task
+    nameMap.put("theTask2", "theTask2b");
+    new ChangeProcessInstanceVersionCommand()
+      .setNodeNameMapping(nameMap)
+      .setTaskNameMapping(nameMap)
+      .setProcessName("testTaskInstances")
+      .execute(jbpmContext);
+
+    newTransaction();
+
+    pi1 = graphSession.loadProcessInstance(pi1.getId());
+    pi2 = graphSession.loadProcessInstance(pi2.getId());
+
+    assertEquals(pd2.getNode("task1").getId(), pi1.getRootToken().getNode().getId());
+    assertEquals(pd2.getNode("task2b").getId(), pi2.getRootToken().getNode().getId());
+
+    TaskInstance ti1 = pi1.getTaskMgmtInstance().getTaskInstances().iterator().next();
+    TaskInstance ti2 = pi2.getTaskMgmtInstance().getTaskInstances().iterator().next();
+
+    assertEquals(pd2.getTaskMgmtDefinition().getTask("theTask1").getId(), ti1.getTask().getId());
+    assertEquals(pd2.getTaskMgmtDefinition().getTask("theTask2b").getId(), ti2.getTask().getId());
+
+    ti1.end("end1");
+    ti2.end("end2b");
+
+    assertEquals(pd2.getNode("end").getId(), pi1.getRootToken().getNode().getId());
+    assertTrue(pi1.hasEnded());
+    assertEquals(pd2.getNode("end").getId(), pi2.getRootToken().getNode().getId());
+    assertTrue(pi2.hasEnded());
+  }
+
+  /**
+   * test if changing process version works correctly
+   * if a timer is included in the process definition.
+   * 
+   * Important: The timer itself IS NOT changed, so 
+   * e.g. used leaving transitions must be still existent
+   */
+  public void testTimerInState() throws Exception
+  {
+    String xmlVersion1 = //
+    "<process-definition name='TestChangeVersion'>" //
+    + "   <start-state name='start'>" //
+    + "      <transition to='timer1' />"//
+    + "   </start-state>" //
+    + "   <state name='timer1'>" //
+    + "      <timer name='timer1' duedate='5 seconds' transition='end' />" //
+    + "      <transition name='end' to='end' />" //
+    + "   </state>" //
+    + "   <end-state name='end'/>" //
+    + "</process-definition>";
+
+    jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion1));
+    pd1 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // start instance
+    ProcessInstance pi1 = jbpmContext.newProcessInstance("TestChangeVersion");
+    pi1.signal();
+    Timer timer = (Timer) session.createQuery("from org.jbpm.job.Timer").uniqueResult();
+
+    // check timer
+    assertNotNull("Timer is null", timer);
+    assertEquals("timer1", timer.getName());
+    assertEquals(pd1.getNode("timer1").getId(), timer.getGraphElement().getId());
+
+    String xmlVersion2 = //
+    "<process-definition name='TestChangeVersion'>" //
+    + "   <start-state name='start'>" //
+    + "      <transition to='timer2' />"//
+    + "   </start-state>" //
+    + "   <state name='timer2'>" //
+    + "      <timer name='timer1' duedate='5 seconds' transition='end1' />" //
+    + "      <transition name='end' to='end' />" //
+    + "   </state>" //
+    + "   <end-state name='end'/>" //
+    + "</process-definition>";
+
+    jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion2));
+    pd2 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // change version
+    HashMap nameMap = new HashMap();
+    nameMap.put("timer1", "timer2");
+    new ChangeProcessInstanceVersionCommand()
+      .setNodeNameMapping(nameMap)
+      .setProcessInstanceId(pi1.getId())
+      .execute(jbpmContext);
+
+    // load changed stuff
+    newTransaction();    
+    pi1 = graphSession.loadProcessInstance(pi1.getId());
+    timer = (Timer) session.createQuery("from org.jbpm.job.Timer").uniqueResult();
+    
+    // and check again
+    assertEquals(pd2.getNode("timer2").getId(), pi1.getRootToken().getNode().getId());
+    assertEquals("timer1", timer.getName());
+    assertEquals(pd2.getNode("timer2").getId(), timer.getGraphElement().getId());
+
+    timer.execute(jbpmContext);
+
+    assertEquals(pd2.getNode("end").getId(), pi1.getRootToken().getNode().getId());
+    assertTrue(pi1.hasEnded());
+  }
+  
+  public void testTimerInTask() throws Exception
+  {   
+    String xmlVersion1 = //
+    "<process-definition name='TestChangeVersion'>" //
+    + "   <start-state name='start'>" //
+    + "      <transition to='timer1' />"//
+    + "   </start-state>" //
+    + "   <task-node name='timer1'>" //
+    + "      <task name='myTask'>" //
+    + "        <timer name='timer1' duedate='5 seconds' transition='end' />" //
+    + "      </task>" //
+    + "      <transition name='end' to='end' />" //
+    + "   </task-node>" //
+    + "   <end-state name='end'/>" //
+    + "</process-definition>";
+
+    jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion1));
+    pd1 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // start instance
+    ProcessInstance pi1 = jbpmContext.newProcessInstance("TestChangeVersion");
+    pi1.signal();
+//    jbpmContext.getJobSession().deleteJobsForProcessInstance(processInstance);
+//    NOT UNIQUE?!
+    Timer timer = (Timer) session.createQuery("from org.jbpm.job.Timer").uniqueResult();
+
+    // check timer
+    assertNotNull("Timer is null", timer);
+    assertEquals("timer1", timer.getName());
+    assertEquals(pd1.getTaskMgmtDefinition().getTask("myTask").getId(), timer.getGraphElement().getId());
+    TaskInstance ti1 = pi1.getTaskMgmtInstance().getTaskInstances().iterator().next();
+    assertEquals(pd1.getTaskMgmtDefinition().getTask("myTask").getId(), ti1.getTask().getId());
+
+    String xmlVersion2 = //
+    "<process-definition name='TestChangeVersion'>" //
+    + "   <start-state name='start'>" //
+    + "      <transition to='timer2' />"//
+    + "   </start-state>" //
+    + "   <task-node name='timer2'>" //
+    + "      <task name='myTask2'>" //
+    + "        <timer name='timer1' duedate='5 seconds' transition='end' />" //
+    + "      </task>" //
+    + "      <transition name='end' to='end' />" //
+    + "   </task-node>" //
+    + "   <end-state name='end'/>" //
+    + "</process-definition>";
+
+    jbpmContext.deployProcessDefinition(ProcessDefinition.parseXmlString(xmlVersion2));
+    pd2 = jbpmContext.getGraphSession().findLatestProcessDefinition("TestChangeVersion");
+
+    // change version
+    HashMap nameMap = new HashMap();
+    nameMap.put("timer1", "timer2");
+    nameMap.put("myTask", "myTask2");
+    new ChangeProcessInstanceVersionCommand()
+      .setNodeNameMapping(nameMap)
+      .setTaskNameMapping(nameMap)
+      .setProcessInstanceId(pi1.getId())
+      .execute(jbpmContext);
+
+    // load changed stuff
+    newTransaction();    
+    pi1 = graphSession.loadProcessInstance(pi1.getId());
+    timer = (Timer) session.createQuery("from org.jbpm.job.Timer").uniqueResult();
+    
+    // and check again
+    assertEquals(pd2.getNode("timer2").getId(), pi1.getRootToken().getNode().getId());
+    assertEquals("timer1", timer.getName());
+    assertEquals(pd2.getTaskMgmtDefinition().getTask("myTask2").getId(), timer.getGraphElement().getId());
+    ti1 = pi1.getTaskMgmtInstance().getTaskInstances().iterator().next();
+    assertEquals(pd2.getTaskMgmtDefinition().getTask("myTask2").getId(), ti1.getTask().getId());
+
+    // and go on
+    timer.execute(jbpmContext);
+
+    assertEquals(pd2.getNode("end").getId(), pi1.getRootToken().getNode().getId());
+    assertTrue(pi1.hasEnded());
+  }  
+  
+  public void testTimerInProcessDefinition() throws Exception
+  {
+    // TODO
+  }
+
+  /**
+   * Asynchronous continuation is not affected by changing the version,
+   * because a {@link Job} only holds {@link ProcessInstance}.id, {@link Token}.id
+   * or {@link TaskInstance}.id. None of them are changed while version changes. 
+   */
+  public void notestAsync()
+  {
+  }
+
+}

Added: jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ProcessInstanceCommandTest.java
===================================================================
--- jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ProcessInstanceCommandTest.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/ProcessInstanceCommandTest.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,168 @@
+package org.jbpm.command;
+
+import java.util.Iterator;
+
+import org.jbpm.JbpmException;
+import org.jbpm.db.AbstractDbTestCase;
+import org.jbpm.graph.def.ProcessDefinition;
+import org.jbpm.graph.exe.ProcessInstance;
+import org.jbpm.graph.exe.Token;
+
+/**
+ * Tests for {@link Command}s working on {@link ProcessInstance} 
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public class ProcessInstanceCommandTest extends AbstractDbTestCase
+{
+  private ProcessDefinition processDefinition;
+  
+  @Override
+  protected void tearDown() throws Exception
+  {
+    if (processDefinition!=null) {
+      jbpmContext.getGraphSession().deleteProcessDefinition(processDefinition.getId());
+    }
+    processDefinition=null;
+    
+    super.tearDown();
+  }
+  
+  public void testEmpty() {}
+
+  public void testCancelProcessInstanceCommand() throws Exception {
+    String xml = //
+      "<?xml version='1.0' encoding='UTF-8'?>"      //
+      +"<process-definition name='TestException'>"  //
+      +"   <start-state name='start'>"              //
+      +"      <transition to='fork' />"             //
+      +"   </start-state>"                          //
+      +"   <fork name='fork'>"                      //
+      +"      <transition name='path1' to='path1' />" //
+      +"      <transition name='path2' to='path2' />" //
+      +"   </fork>"                                 //
+      +"   <state name='path1'>"                    //
+      +"      <transition to='join' />"             //
+      +"   </state>"                                //
+      +"   <state name='path2'>"                    //
+      +"      <transition to='join' />"             //
+      +"   </state>"                                //
+      +"   <join name='join'>"                      //
+      +"      <transition to='end' />"              //
+      +"   </join>"                                 //
+      +"   <end-state name='end' />"                //
+      +"</process-definition>";
+    
+    processDefinition = ProcessDefinition.parseXmlString(xml);      
+    jbpmContext.deployProcessDefinition(processDefinition);
+    ProcessInstance processInstance = processDefinition.createProcessInstance();
+    processInstance.getRootToken().signal();
+    processInstance = saveAndReload(processInstance);
+    
+    assertFalse(processInstance.getRootToken().hasEnded());
+    assertEquals("fork", processInstance.getRootToken().getNode().getName());
+    for (Iterator iterator = processInstance.getRootToken().getChildren().values().iterator(); iterator.hasNext();)
+    {
+      Token childToken = (Token)iterator.next();
+      assertFalse(childToken.hasEnded());
+    }
+
+    // execute CancelProcessInstanceCommand
+    new CancelProcessInstanceCommand(processInstance.getId()).execute(jbpmContext);
+    
+    // and verify it is canceled
+    assertTrue(processInstance.getRootToken().hasEnded());
+    assertEquals("fork", processInstance.getRootToken().getNode().getName());
+    for (Iterator iterator = processInstance.getRootToken().getChildren().values().iterator(); iterator.hasNext();)
+    {
+      Token childToken = (Token)iterator.next();
+      assertTrue(childToken.hasEnded());
+    }
+  }
+    
+  public void testSuspendResumeProcessInstanceCommand() throws Exception {
+    String xml = //
+      "<?xml version='1.0' encoding='UTF-8'?>"      //
+      +"<process-definition name='TestException'>"  //
+      +"   <start-state name='start'>"              //
+      +"      <transition to='fork' />"             //
+      +"   </start-state>"                          //
+      +"   <fork name='fork'>"                      //
+      +"      <transition name='path1' to='path1' />" //
+      +"      <transition name='path2' to='path2' />" //
+      +"   </fork>"                                 //
+      +"   <state name='path1'>"                    //
+      +"      <transition to='join' />"             //
+      +"   </state>"                                //
+      +"   <state name='path2'>"                    //
+      +"      <transition to='join' />"             //
+      +"   </state>"                                //
+      +"   <join name='join'>"                      //
+      +"      <transition to='end' />"              //
+      +"   </join>"                                 //
+      +"   <end-state name='end' />"                //
+      +"</process-definition>";
+    
+    processDefinition = ProcessDefinition.parseXmlString(xml);      
+    jbpmContext.deployProcessDefinition(processDefinition);
+    ProcessInstance processInstance = processDefinition.createProcessInstance();
+    processInstance.getRootToken().signal();
+    processInstance = saveAndReload(processInstance);
+    
+    assertFalse(processInstance.isSuspended());
+    assertFalse(processInstance.getRootToken().isSuspended());
+    assertEquals("fork", processInstance.getRootToken().getNode().getName());
+    for (Iterator iterator = processInstance.getRootToken().getChildren().values().iterator(); iterator.hasNext();)
+    {
+      Token childToken = (Token)iterator.next();
+      assertFalse(childToken.isSuspended());
+    }
+
+    // execute SuspendProcessInstanceCommand
+    new SuspendProcessInstanceCommand().setProcessInstanceId(processInstance.getId()).execute(jbpmContext);
+    
+    // and verify
+    assertTrue(processInstance.isSuspended());
+    assertTrue(processInstance.getRootToken().isSuspended());
+    assertEquals("fork", processInstance.getRootToken().getNode().getName());
+    for (Iterator iterator = processInstance.getRootToken().getChildren().values().iterator(); iterator.hasNext();)
+    {
+      Token childToken = (Token)iterator.next();
+      assertTrue(childToken.isSuspended());
+      
+      try {
+        childToken.signal();
+        fail("signal should not be accepted on suspended token");
+      }
+      catch (Exception ex) {
+        assertEquals(JbpmException.class, ex.getClass());
+        // can't signal token 'path1' (5): it is suspended
+        assertTrue("exception should be, that token is suspended", ex.getMessage().indexOf("it is suspended")>0);
+      }
+    }
+    
+    // execute ResumeProcessInstanceCommand
+    new ResumeProcessInstanceCommand().setProcessInstanceId(processInstance.getId()).execute(jbpmContext);
+
+    // and verify
+    assertFalse(processInstance.isSuspended());
+    assertFalse(processInstance.getRootToken().isSuspended());
+    for (Iterator iterator = processInstance.getRootToken().getChildren().values().iterator(); iterator.hasNext();)
+    {
+      Token childToken = (Token)iterator.next();
+      assertFalse(childToken.isSuspended());
+      childToken.signal();
+    }
+    
+    assertEquals("end", processInstance.getRootToken().getNode().getName());
+    assertTrue(processInstance.hasEnded());
+
+    // check db state
+    processInstance = saveAndReload(processInstance);    
+
+    assertEquals("end", processInstance.getRootToken().getNode().getName());
+    assertTrue(processInstance.hasEnded());
+  }  
+  
+
+}

Added: jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/TokenCommandTest.java
===================================================================
--- jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/TokenCommandTest.java	                        (rev 0)
+++ jbpm3/trunk/modules/core/src/test/java/org/jbpm/command/TokenCommandTest.java	2008-12-10 12:58:42 UTC (rev 3316)
@@ -0,0 +1,78 @@
+package org.jbpm.command;
+
+import org.jbpm.JbpmException;
+import org.jbpm.db.AbstractDbTestCase;
+import org.jbpm.graph.def.ProcessDefinition;
+import org.jbpm.graph.exe.ProcessInstance;
+import org.jbpm.graph.exe.Token;
+
+/**
+ * Tests for {@link Command}s working on {@link Token}
+ * 
+ * @author bernd.ruecker at camunda.com
+ */
+public class TokenCommandTest extends AbstractDbTestCase
+{
+  
+  public void testUnlockTokenCommand() throws Exception {
+    String xml = //
+       "<process-definition name='TestException'>"  //
+      +"   <start-state name='start'>"              //
+      +"      <transition to='wait' />"             //
+      +"   </start-state>"                          //
+      +"   <state name='wait'>"                     //
+      +"      <transition to='end' />"              //
+      +"   </state>"                                //
+      +"   <end-state name='end' />"                //
+      +"</process-definition>";
+    
+    ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(xml);
+    jbpmContext.deployProcessDefinition(processDefinition);
+    try {
+      ProcessInstance processInstance =  jbpmContext.newProcessInstanceForUpdate("TestException");//processDefinition.createProcessInstance();
+      processInstance.getRootToken().signal();
+      processInstance.getRootToken().lock("TEST-OWNER");
+      long tokenId = processInstance.getRootToken().getId();
+      
+      processInstance = saveAndReload(processInstance);    
+      try {
+        processInstance.getRootToken().signal();
+        fail("TOKEN IS LOCKED exception expected");
+      }
+      catch (JbpmException ex) {
+        // org.jbpm.JbpmException: this token is locked by TEST-OWNER
+        assertEquals("this token is locked by TEST-OWNER", ex.getMessage());
+      }
+  
+      // unlocking without owner is a force unlock -> works
+      new UnlockTokenCommand().setTokenId(tokenId).execute(jbpmContext);
+//      Token token = jbpmContext.loadTokenForUpdate(processInstance.getRootToken().getId());
+//      token.foreUnlock();
+  
+      // unlock with same owner
+      processInstance = saveAndReload(processInstance);    
+      processInstance.getRootToken().lock("TEST-OWNER");
+      processInstance = saveAndReload(processInstance);    
+      new UnlockTokenCommand().setLockOwner("TEST-OWNER").setTokenId(tokenId).execute(jbpmContext);
+  
+      // unlocking with wrong owner fails
+      processInstance = saveAndReload(processInstance);    
+      processInstance.getRootToken().lock("TEST-OWNER");
+      processInstance = saveAndReload(processInstance);    
+      try {
+        new UnlockTokenCommand().setLockOwner("OTHER-OWNER").setTokenId(tokenId).execute(jbpmContext);
+        fail("'OTHER-OWNER' can't unlock token exception expected");
+      }
+      catch (JbpmException ex) {
+        // org.jbpm.JbpmException: 'OTHER-OWNER' can't unlock token '1' because it was already locked by 'TEST-OWNER'
+        assertTrue("Wrong exception, wasn't 'OTHER-OWNER' can't unlock token", ex.getMessage().indexOf("'OTHER-OWNER' can't unlock token")>=0);
+      }
+    }
+    finally {
+      newTransaction();
+      jbpmContext.getGraphSession().deleteProcessDefinition(processDefinition.getId());      
+    }
+  }
+  
+
+}




More information about the jbpm-commits mailing list