提交 48c1d888 编写于 作者: T tombaeyens

ACT-60 expanding pvm interpretation algorithm to handle more scope,...

ACT-60 expanding pvm interpretation algorithm to handle more scope, concurrency and end combinations
上级 a125ef04
......@@ -104,9 +104,6 @@ public class ProcessDefinitionBuilder {
public ProcessDefinitionBuilder behavior(ActivityBehavior activityBehaviour) {
getActivity().setActivityBehavior(activityBehaviour);
if (activityBehaviour instanceof CompositeActivityBehavior) {
getActivity().setScope(true);
}
return this;
}
......@@ -131,4 +128,9 @@ public class ProcessDefinitionBuilder {
protected ActivityImpl getActivity() {
return (ActivityImpl) scopeStack.peek();
}
public ProcessDefinitionBuilder scope() {
getActivity().setScope(true);
return this;
}
}
......@@ -13,7 +13,6 @@
package org.activiti.pvm.activity;
import org.activiti.pvm.process.PvmActivity;
/**
......@@ -21,5 +20,5 @@ import org.activiti.pvm.process.PvmActivity;
*/
public interface CompositeActivityBehavior extends ActivityBehavior {
void lastExecutionEnded(ActivityExecution execution, PvmActivity scope, ActivityExecution nestedExecution);
void lastExecutionEnded(ActivityExecution execution);
}
......@@ -12,6 +12,8 @@
*/
package org.activiti.pvm.impl.runtime;
import java.util.logging.Logger;
import org.activiti.pvm.impl.process.ActivityImpl;
import org.activiti.pvm.impl.process.ScopeImpl;
import org.activiti.pvm.impl.process.TransitionImpl;
......@@ -21,27 +23,58 @@ import org.activiti.pvm.impl.process.TransitionImpl;
* @author Tom Baeyens
*/
public class AtomicOperationTransitionDestroyScope implements AtomicOperation {
private static Logger log = Logger.getLogger(AtomicOperationTransitionDestroyScope.class.getName());
public void execute(ExecutionImpl execution) {
ExecutionImpl propagatingExecution = null;
ActivityImpl activity = execution.getActivity();
// if this transition is crossing a scope boundary
if (activity.isScope()) {
ExecutionImpl parentScopeInstance = null;
if (execution.isConcurrent()) {
// if this is a concurrent execution crossing a scope boundary
if (execution.isConcurrent() && !execution.isScope()) {
// first remove the execution from the current root
ExecutionImpl concurrentRoot = execution.getParent();
parentScopeInstance = execution.getParent().getParent();
} else {
parentScopeInstance = execution.getParent();
}
if (parentScopeInstance.isActive()) {
throw new UnsupportedOperationException("not implemented yet");
log.fine("moving concurrent "+execution+" one scope up under "+parentScopeInstance);
concurrentRoot.removeExecution(execution);
parentScopeInstance.getExecutions().add(execution);
execution.setParent(parentScopeInstance);
execution.setActivity(activity);
propagatingExecution = execution;
// if there is only a single concurrent execution left
// in the concurrent root, auto-prune it. meaning, the
// last concurrent child execution data should be cloned into
// the concurrent root.
if (concurrentRoot.getExecutions().size()==1) {
ExecutionImpl lastConcurrent = concurrentRoot.getExecutions().get(0);
log.fine("replacing concurrent root "+concurrentRoot+" with last concurrent "+lastConcurrent);
// We can't just merge the data of the lastConcurrent into the concurrentRoot.
// This is because the concurrent root might be in a takeAll-loop. So the
// concurrent execution is the one that will be receiveing the take
parentScopeInstance.getExecutions().remove(concurrentRoot);
parentScopeInstance.getExecutions().add(lastConcurrent);
lastConcurrent.setParent(parentScopeInstance);
lastConcurrent.setActive(true);
lastConcurrent.setScope(true);
// TODO extract common, overridable destroy method
}
} else if (execution.isConcurrent() && execution.isScope()) {
log.fine("scoped concurrent "+execution+" becomes concurrent and remains under "+execution.getParent());
execution.setScope(false);
propagatingExecution = execution;
} else {
propagatingExecution = parentScopeInstance;
propagatingExecution = execution.destroyScope();
}
execution.destroyScope();
} else {
propagatingExecution = execution;
......
......@@ -140,7 +140,7 @@ public class ExecutionImpl implements
// executions ///////////////////////////////////////////////////////////////
/** ensures initialization and returns the non-null executions list */
public List<? extends ExecutionImpl> getExecutions() {
public List<ExecutionImpl> getExecutions() {
ensureExecutionsInitialized();
return executions;
}
......@@ -235,21 +235,21 @@ public class ExecutionImpl implements
// if there is a parent
ensureParentInitialized();
if (parent!=null) {
ActivityImpl parentScopeActivity = activity.getParentActivity();
while(parentScopeActivity!=null && !parentScopeActivity.isScope()) {
parentScopeActivity = parentScopeActivity.getParentActivity();
activity = activity.getParentActivity();
while(activity!=null && !activity.isScope()) {
// TODO add destroy scope if activity is scope
activity = activity.getParentActivity();
}
ExecutionImpl parent = this.parent;
// remove the bidirectional relation
this.parent.removeExecution(this);
if (parentScopeActivity!=null && parent.getExecutions().isEmpty()) {
ActivityBehavior activityBehavior = parentScopeActivity.getActivityBehavior();
if (activity!=null && parent.getExecutions().size()==1) {
ActivityBehavior activityBehavior = activity.getActivityBehavior();
if (activityBehavior instanceof CompositeActivityBehavior) {
((CompositeActivityBehavior) activityBehavior).lastExecutionEnded(parent, parentScopeActivity, this);
((CompositeActivityBehavior) activityBehavior).lastExecutionEnded(this);
} else {
end();
}
} else {
this.parent.removeExecution(this);
}
} else { // this execution is a process instance
......@@ -288,9 +288,11 @@ public class ExecutionImpl implements
}
protected void collectActiveActivityIds(List<String> activeActivityIds) {
ensureActivityInitialized();
if (isActive && activity!=null) {
activeActivityIds.add(activity.getId());
}
ensureExecutionsInitialized();
for (ExecutionImpl execution: executions) {
execution.collectActiveActivityIds(activeActivityIds);
}
......@@ -305,7 +307,9 @@ public class ExecutionImpl implements
protected void removeExecution(ExecutionImpl execution) {
ensureExecutionsInitialized();
executions.remove(execution);
execution.setParent(null);
// we don't remove the parent as that would make it a process instance
// and there is no need for setting the parent to null
// execution.setParent(null);
}
/** must be called before memberfield executions is used.
......@@ -375,6 +379,7 @@ public class ExecutionImpl implements
public ExecutionImpl createScope() {
ExecutionImpl scopeExecution = createExecution();
scopeExecution.setScope(true);
scopeExecution.setTransition(getTransition());
setTransition(null);
setActive(false);
......@@ -382,13 +387,14 @@ public class ExecutionImpl implements
return scopeExecution;
}
public void destroyScope() {
public ExecutionImpl destroyScope() {
log.fine("destroy scope: scoped "+this+" continues as parent scope "+getParent());
parent.setActivity(getActivity());
parent.setTransition(getTransition());
parent.setActive(true);
parent.removeExecution(this);
end();
return parent;
}
// process instance start implementation ////////////////////////////////////
......@@ -447,25 +453,37 @@ public class ExecutionImpl implements
return inactiveConcurrentExecutionsInActivity;
}
public void takeAll(List<PvmTransition> transitions, List<ActivityExecution> joinedExecutions) {
@SuppressWarnings("unchecked")
public void takeAll(List<PvmTransition> transitions, List<ActivityExecution> recyclableExecutions) {
transitions = new ArrayList<PvmTransition>(transitions);
joinedExecutions = new ArrayList<ActivityExecution>(joinedExecutions);
recyclableExecutions = new ArrayList<ActivityExecution>(recyclableExecutions);
ExecutionImpl concurrentRoot = (isConcurrent() ? getParent() : this);
List< ? extends ActivityExecution> concurrentExecutions = concurrentRoot.getExecutions();
List<ExecutionImpl> concurrentActiveExecutions = new ArrayList<ExecutionImpl>();
for (ExecutionImpl execution: concurrentRoot.getExecutions()) {
if (execution.isActive()) {
concurrentActiveExecutions.add(execution);
}
}
if (log.isLoggable(Level.FINE)) {
log.fine("transitions to take concurrent: " + transitions);
log.fine("existing concurrent executions: " + concurrentExecutions);
log.fine("active concurrent executions: " + concurrentActiveExecutions);
}
if ( (transitions.size()==1)
&& (joinedExecutions.size()==concurrentExecutions.size())
&& (concurrentActiveExecutions.isEmpty())
) {
for (ActivityExecution prunedExecution: joinedExecutions) {
log.info("pruning execution "+prunedExecution);
prunedExecution.end();
List<ExecutionImpl> recyclableExecutionImpls = (List) recyclableExecutions;
for (ExecutionImpl prunedExecution: recyclableExecutionImpls) {
// End the pruned executions if necessary.
// Some recyclable executions are inactivated (joined executions)
// Others are already ended (end activities)
if (!prunedExecution.isEnded()) {
log.info("pruning execution " + prunedExecution);
prunedExecution.getParent().removeExecution(prunedExecution);
}
}
log.info("activating the concurrent root execution as the single path of execution going forward");
......@@ -477,27 +495,28 @@ public class ExecutionImpl implements
} else {
List<OutgoingExecution> outgoingExecutions = new ArrayList<OutgoingExecution>();
joinedExecutions.remove(concurrentRoot);
log.fine("joined executions to be reused: " + joinedExecutions);
recyclableExecutions.remove(concurrentRoot);
log.fine("recyclable executions for reused: " + recyclableExecutions);
// first create the concurrent executions
while (!transitions.isEmpty()) {
PvmTransition outgoingTransition = transitions.remove(0);
if (joinedExecutions.isEmpty()) {
if (recyclableExecutions.isEmpty()) {
ActivityExecution outgoingExecution = concurrentRoot.createExecution();
outgoingExecutions.add(new OutgoingExecution(outgoingExecution, outgoingTransition, true));
log.fine("new "+outgoingExecution+" created to take transition "+outgoingTransition);
} else {
ActivityExecution outgoingExecution = joinedExecutions.remove(0);
ActivityExecution outgoingExecution = recyclableExecutions.remove(0);
outgoingExecutions.add(new OutgoingExecution(outgoingExecution, outgoingTransition, true));
log.fine("recycled "+outgoingExecution+" to take transition "+outgoingTransition);
}
}
// prune the executions that are not recycled
for (ActivityExecution prunedExecution: joinedExecutions) {
for (ActivityExecution prunedExecution: recyclableExecutions) {
log.info("pruning execution "+prunedExecution);
prunedExecution.end();
}
......@@ -578,6 +597,7 @@ public class ExecutionImpl implements
public void setVariable(String variableName, Object value) {
ensureVariableMapInitialized();
log.fine("setting variable '"+variableName+"' to value '"+value+"'");
variableMap.put(variableName, value);
}
......@@ -595,20 +615,13 @@ public class ExecutionImpl implements
// toString /////////////////////////////////////////////////////////////////
public String toString() {
return toString(new StringBuilder());
}
public String toString(StringBuilder text) {
if (isProcessInstance()) {
text.append("ProcessInstance");
return "ProcessInstance["+System.identityHashCode(this)+"]";
} else {
text.append("Execution");
return "Execution["+System.identityHashCode(this)+"]";
}
text.append("-");
text.append(getIdForToString());
return text.toString();
}
protected String getIdForToString() {
return Integer.toString(System.identityHashCode(this));
}
......
package org.activiti.pvm.test;
import java.util.ArrayList;
import org.activiti.pvm.ProcessDefinitionBuilder;
import org.activiti.pvm.process.PvmProcessDefinition;
import org.activiti.pvm.runtime.PvmExecution;
import org.activiti.pvm.runtime.PvmProcessInstance;
import org.activiti.test.pvm.activities.Automatic;
import org.activiti.test.pvm.activities.End;
import org.activiti.test.pvm.activities.WaitState;
import org.activiti.test.pvm.activities.While;
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Tom Baeyens
*/
public class PvmBasicLinearExecutionTest extends PvmTestCase {
public void testStartEnd() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("start")
.initial()
.behavior(new Automatic())
.transition("end")
.endActivity()
.createActivity("end")
.behavior(new End())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
}
public void testSingleAutomatic() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("one")
.initial()
.behavior(new Automatic())
.transition("two")
.endActivity()
.createActivity("two")
.behavior(new Automatic())
.transition("three")
.endActivity()
.createActivity("three")
.behavior(new End())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
}
public void testSingleWaitState() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("one")
.initial()
.behavior(new Automatic())
.transition("two")
.endActivity()
.createActivity("two")
.behavior(new WaitState())
.transition("three")
.endActivity()
.createActivity("three")
.behavior(new End())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
PvmExecution activityInstance = processInstance.findExecution("two");
assertNotNull(activityInstance);
activityInstance.signal(null, null);
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
}
public void testCombinationOfWaitStatesAndAutomatics() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("start")
.initial()
.behavior(new Automatic())
.transition("one")
.endActivity()
.createActivity("one")
.behavior(new WaitState())
.transition("two")
.endActivity()
.createActivity("two")
.behavior(new WaitState())
.transition("three")
.endActivity()
.createActivity("three")
.behavior(new Automatic())
.transition("four")
.endActivity()
.createActivity("four")
.behavior(new Automatic())
.transition("five")
.endActivity()
.createActivity("five")
.behavior(new End())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
PvmExecution activityInstance = processInstance.findExecution("one");
assertNotNull(activityInstance);
activityInstance.signal(null, null);
activityInstance = processInstance.findExecution("two");
assertNotNull(activityInstance);
activityInstance.signal(null, null);
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
}
public void testWhileLoop() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("start")
.initial()
.behavior(new Automatic())
.transition("loop")
.endActivity()
.createActivity("loop")
.behavior(new While("count", 0, 1000))
.transition("one", "more")
.transition("end", "done")
.endActivity()
.createActivity("one")
.behavior(new Automatic())
.transition("two")
.endActivity()
.createActivity("two")
.behavior(new Automatic())
.transition("three")
.endActivity()
.createActivity("three")
.behavior(new Automatic())
.transition("loop")
.endActivity()
.createActivity("end")
.behavior(new End())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
}
}
......@@ -38,6 +38,7 @@ public class PvmEmbeddedSubProcessTest extends PvmTestCase {
.transition("embeddedsubprocess")
.endActivity()
.createActivity("embeddedsubprocess")
.scope()
.behavior(new EmbeddedSubProcess())
.createActivity("startInside")
.behavior(new Automatic())
......
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.pvm.test;
import java.util.ArrayList;
import org.activiti.pvm.ProcessDefinitionBuilder;
import org.activiti.pvm.process.PvmProcessDefinition;
import org.activiti.pvm.runtime.PvmExecution;
import org.activiti.pvm.runtime.PvmProcessInstance;
import org.activiti.test.pvm.activities.Automatic;
import org.activiti.test.pvm.activities.End;
import org.activiti.test.pvm.activities.WaitState;
/**
* @author Tom Baeyens
*/
public class PvmFunctionalActivityScopeTest extends PvmTestCase {
public void testWaitStateScope() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("start")
.initial()
.behavior(new Automatic())
.transition("scopedWait")
.endActivity()
.createActivity("scopedWait")
.scope()
.behavior(new WaitState())
.transition("end")
.endActivity()
.createActivity("end")
.behavior(new End())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
PvmExecution activityInstance = processInstance.findExecution("scopedWait");
assertNotNull(activityInstance);
activityInstance.signal(null, null);
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
}
public void testNestedScope() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("start")
.initial()
.behavior(new Automatic())
.transition("scopedWait")
.endActivity()
.createActivity("outerOne")
.scope()
.behavior(new WaitState())
.transition("end")
.endActivity()
.createActivity("scopedWait")
.scope()
.behavior(new WaitState())
.transition("end")
.endActivity()
.createActivity("end")
.behavior(new End())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
PvmExecution activityInstance = processInstance.findExecution("scopedWait");
assertNotNull(activityInstance);
activityInstance.signal(null, null);
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
}
}
package org.activiti.pvm.test;
import java.util.ArrayList;
import org.activiti.pvm.ProcessDefinitionBuilder;
import org.activiti.pvm.process.PvmProcessDefinition;
import org.activiti.pvm.runtime.PvmExecution;
import org.activiti.pvm.runtime.PvmProcessInstance;
import org.activiti.test.pvm.activities.Automatic;
import org.activiti.test.pvm.activities.End;
import org.activiti.test.pvm.activities.WaitState;
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
......@@ -23,40 +11,57 @@ import org.activiti.test.pvm.activities.WaitState;
* limitations under the License.
*/
package org.activiti.pvm.test;
import java.util.ArrayList;
import java.util.List;
import org.activiti.pvm.ProcessDefinitionBuilder;
import org.activiti.pvm.process.PvmProcessDefinition;
import org.activiti.pvm.runtime.PvmProcessInstance;
import org.activiti.test.pvm.activities.Automatic;
import org.activiti.test.pvm.activities.End;
import org.activiti.test.pvm.activities.ParallelGateway;
import org.activiti.test.pvm.activities.WaitState;
/**
* @author Tom Baeyens
*/
public class PvmBasicActivitiesTest extends PvmTestCase {
public class PvmParallelScopeTest extends PvmTestCase {
public void testBasicLinearActivities() {
public void testConcurrentPathsComingOutOfScope() {
PvmProcessDefinition processDefinition = new ProcessDefinitionBuilder()
.createActivity("start")
.initial()
.behavior(new Automatic())
.transition("one")
.transition("fork")
.endActivity()
.createActivity("one")
.behavior(new WaitState())
.transition("two")
.createActivity("scope")
.scope()
.createActivity("fork")
.behavior(new ParallelGateway())
.transition("c1")
.transition("c2")
.endActivity()
.endActivity()
.createActivity("two")
.behavior(new Automatic())
.transition("three")
.createActivity("c1")
.behavior(new WaitState())
.endActivity()
.createActivity("three")
.behavior(new End())
.createActivity("c2")
.behavior(new WaitState())
.endActivity()
.buildProcessDefinition();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
PvmProcessInstance processInstance = processDefinition.createProcessInstance();
processInstance.start();
PvmExecution activityInstance = processInstance.findExecution("one");
assertNotNull(activityInstance);
List<String> activeActivityIds = processInstance.findActiveActivityIds();
List<String> expectedActiveActivityIds = new ArrayList<String>();
expectedActiveActivityIds.add("c1");
expectedActiveActivityIds.add("c2");
activityInstance.signal(null, null);
assertEquals(new ArrayList<String>(), processInstance.findActiveActivityIds());
assertTrue(processInstance.isEnded());
assertEquals(expectedActiveActivityIds, activeActivityIds);
}
}
......@@ -41,11 +41,9 @@ public class EmbeddedSubProcess implements CompositeActivityBehavior {
}
}
public void lastExecutionEnded(ActivityExecution execution, PvmActivity scope, ActivityExecution nestedExecution) {
List<PvmTransition> outgoingTransitions = scope.getOutgoingTransitions();
List<ActivityExecution> recyclableExecutions = new ArrayList<ActivityExecution>();
recyclableExecutions.add(nestedExecution);
execution.takeAll(outgoingTransitions, recyclableExecutions);
public void lastExecutionEnded(ActivityExecution execution) {
List<PvmTransition> outgoingTransitions = execution.getActivity().getOutgoingTransitions();
execution.takeAll(outgoingTransitions, new ArrayList<ActivityExecution>());
}
......
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.test.pvm.activities;
import org.activiti.pvm.activity.ActivityBehavior;
import org.activiti.pvm.activity.ActivityExecution;
import org.activiti.pvm.process.PvmTransition;
/**
* @author Tom Baeyens
*/
public class While implements ActivityBehavior {
String variableName;
int from;
int to;
public While(String variableName, int from, int to) {
this.variableName = variableName;
this.from = from;
this.to = to;
}
public void execute(ActivityExecution execution) throws Exception {
PvmTransition more = execution.getActivity().findOutgoingTransition("more");
PvmTransition done = execution.getActivity().findOutgoingTransition("done");
Integer value = (Integer) execution.getVariable(variableName);
if (value==null) {
execution.setVariable(variableName, from);
execution.take(more);
} else {
value = value+1;
if (value<to) {
execution.setVariable(variableName, value);
execution.take(more);
} else {
execution.take(done);
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册