ALF-10197, Added the ability to auto-complete Start Tasks in Activiti. If a start task extends the bpm:activitiStartTask type or implements the bpm:endAutomatically aspect then the task will be ended as soon as the workflow instance is started.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30750 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
N Smith
2011-09-25 17:13:56 +00:00
parent 74b9ff5845
commit eb3404c462
10 changed files with 164 additions and 116 deletions

View File

@@ -335,6 +335,18 @@
</type>
<!-- -->
<!-- Default Activiti Start Task -->
<!-- Ends automatically when workflow starts -->
<!-- -->
<type name="bpm:activitiStartTask">
<parent>bpm:startTask</parent>
<mandatory-aspects>
<aspect>bpm:endAutomatically</aspect>
</mandatory-aspects>
</type>
<!-- -->
<!-- Workflow Definition -->
<!-- -->
@@ -490,6 +502,11 @@
</aspect>
</aspects>
<!-- -->
<!-- Amy start task implementing this marker aspect is automatically ended as soon as the workflow starts -->
<!-- Note this only applies to the Activiti Workflow Engine -->
<!-- -->
<aspect name="bpm:endAutomatically" />
</aspects>
</model>

View File

@@ -24,6 +24,7 @@ import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
/**
* @author Nick Smith
@@ -58,7 +59,8 @@ public abstract class AlfrescoBpmEngine extends BPMEngine
throw new WorkflowException("NamespaceService not specified");
}
WorkflowQNameConverter qNameConverter = new WorkflowQNameConverter(namespaceService);
this.factory = new WorkflowObjectFactory(qNameConverter, tenantService, messageService, dictionaryService, getEngineId());
QName defaultStartTaskType = getDefaultStartTaskType();
this.factory = new WorkflowObjectFactory(qNameConverter, tenantService, messageService, dictionaryService, getEngineId(), defaultStartTaskType);
}
/**
@@ -114,4 +116,6 @@ public abstract class AlfrescoBpmEngine extends BPMEngine
{
this.authorityManager = authorityManager;
}
protected abstract QName getDefaultStartTaskType();
}

View File

@@ -74,6 +74,12 @@ public interface WorkflowModel
static final QName TYPE_ACTIVTI_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "activitiOutcomeTask");
static final QName PROP_OUTCOME_PROPERTY_NAME= QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "outcomePropertyName");
// Activiti Start Task Constants
static final QName TYPE_ACTIVTI_START_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "activitiStartTask");
// Activiti Start Task Constants
static final QName ASPECT_END_AUTOMATICALLY= QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "endAutomatically");
// workflow package
static final QName ASPECT_WORKFLOW_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowPackage");
static final QName PROP_IS_SYSTEM_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "isSystemPackage");

View File

@@ -62,18 +62,20 @@ public class WorkflowObjectFactory
private final MessageService messageService;
private final DictionaryService dictionaryService;
private final String engineId;
private final QName defaultStartTaskType;
public WorkflowObjectFactory(WorkflowQNameConverter qNameConverter,
TenantService tenantService,
MessageService messageService,
DictionaryService dictionaryService,
String engineId)
String engineId, QName defaultStartTaskType)
{
this.tenantService = tenantService;
this.messageService = messageService;
this.dictionaryService = dictionaryService;
this.engineId = engineId;
this.qNameConverter = qNameConverter;
this.defaultStartTaskType = defaultStartTaskType;
}
public String buildGlobalId(String localId)
@@ -390,7 +392,7 @@ public class WorkflowObjectFactory
}
if (typeDef == null)
{
QName defaultTypeName = isStart? WorkflowModel.TYPE_START_TASK : WorkflowModel.TYPE_WORKFLOW_TASK;
QName defaultTypeName = isStart? defaultStartTaskType : WorkflowModel.TYPE_WORKFLOW_TASK;
typeDef = dictionaryService.getType(defaultTypeName);
if (typeDef == null)
{

View File

@@ -357,14 +357,31 @@ public class ActivitiTypeConverter
taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties);
}
public WorkflowTask getVirtualStartTask(String executionId, boolean inProgress)
public WorkflowTask getVirtualStartTask(String processInstanceId, Boolean inProgress)
{
Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(execution.getProcessInstanceId())
.singleResult();
ProcessInstance processInstance = activitiUtil.getProcessInstance(processInstanceId);
if(processInstance != null)
{
if(null == inProgress)
{
inProgress = isStartTaskActive(processInstanceId);
}
return getVirtualStartTask(processInstance, inProgress);
}
HistoricProcessInstance historicProcessInstance = activitiUtil.getHistoricProcessInstance(processInstanceId);
return getVirtualStartTask(historicProcessInstance);
}
String id = ActivitiConstants.START_TASK_PREFIX + execution.getProcessInstanceId();
public boolean isStartTaskActive(String processInstanceId)
{
Object endDate = runtimeService.getVariable(processInstanceId, ActivitiConstants.PROP_START_TASK_END_DATE);
return endDate == null;
}
private WorkflowTask getVirtualStartTask(ProcessInstance processInstance, boolean inProgress)
{
String processInstanceId = processInstance.getId();
String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId;
WorkflowTaskState state = null;
if(inProgress)
@@ -376,7 +393,7 @@ public class ActivitiTypeConverter
state = WorkflowTaskState.COMPLETED;
}
WorkflowPath path = convert(execution);
WorkflowPath path = convert((Execution)processInstance);
// Convert start-event to start-task Node
ReadOnlyProcessDefinition procDef = activitiUtil.getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
@@ -393,7 +410,7 @@ public class ActivitiTypeConverter
// Add properties based on HistoricProcessInstance
HistoricProcessInstance historicProcessInstance = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(execution.getProcessInstanceId())
.processInstanceId(processInstance.getId())
.singleResult();
Map<QName, Serializable> properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, !inProgress);
@@ -406,7 +423,7 @@ public class ActivitiTypeConverter
taskDef, taskDef.getId(), defaultTitle, defaultDescription, state, path, properties);
}
public WorkflowTask getVirtualStartTask(HistoricProcessInstance historicProcessInstance)
private WorkflowTask getVirtualStartTask(HistoricProcessInstance historicProcessInstance)
{
if(historicProcessInstance == null)
{
@@ -416,17 +433,8 @@ public class ActivitiTypeConverter
String processInstanceId = historicProcessInstance.getId();
String id = ActivitiConstants.START_TASK_PREFIX + processInstanceId;
WorkflowTaskState state = null;
boolean completed = historicProcessInstance.getEndTime() != null;
if(completed)
{
state = WorkflowTaskState.COMPLETED;
}
else
{
state = WorkflowTaskState.IN_PROGRESS;
}
// Since the process instance is complete the Start Task must be complete!
WorkflowTaskState state = WorkflowTaskState.COMPLETED;
// We use the process-instance ID as execution-id. It's ended anyway
WorkflowPath path = buildCompletedPath(processInstanceId, processInstanceId);
@@ -442,6 +450,7 @@ public class ActivitiTypeConverter
String taskDefId = activitiUtil.getStartFormKey(historicProcessInstance.getProcessDefinitionId());
WorkflowTaskDefinition taskDef = factory.createTaskDefinition(taskDefId, startNode, taskDefId, true);
boolean completed = historicProcessInstance.getEndTime() != null;
Map<QName, Serializable> properties = propertyConverter.getStartTaskProperties(historicProcessInstance, taskDefId, completed);
// TODO: Figure out what name/description should be used for the start-task, start event's name?

View File

@@ -33,6 +33,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.FormService;
@@ -81,6 +82,7 @@ import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.WorkflowNodeConverter;
import org.alfresco.repo.workflow.WorkflowObjectFactory;
import org.alfresco.repo.workflow.activiti.properties.ActivitiPropertyConverter;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
@@ -689,44 +691,40 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
/**
* {@inheritDoc}
*/
public List<WorkflowTask> getTasksForWorkflowPath(String pathId)
{
try
{
// Extract the Activiti ID from the path
String executionId = getExecutionIdFromPath(pathId);
if(executionId == null)
if (executionId == null)
{
throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_INVALID, pathId));
}
// Check if the execution exists
Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();
if(execution == null)
if (execution == null)
{
throw new WorkflowException(messageService.getMessage(ERR_GET_WORKFLOW_TOKEN_NULL, pathId));
}
List<WorkflowTask> resultList = new ArrayList<WorkflowTask>();
// Check if workflow's start task has been completed. If not, return the virtual task
// Otherwise, just return the runtime activiti tasks
Date startTaskEndDate = (Date) runtimeService.getVariable(execution.getProcessInstanceId(),
ActivitiConstants.PROP_START_TASK_END_DATE);
boolean startTaskEnded = (startTaskEndDate != null);
if(startTaskEnded)
// Check if workflow's start task has been completed. If not, return
// the virtual task
// Otherwise, just return the runtime Activiti tasks
String processInstanceId = execution.getProcessInstanceId();
ArrayList<WorkflowTask> resultList = new ArrayList<WorkflowTask>();
if (typeConverter.isStartTaskActive(processInstanceId))
{
List<Task> tasks = taskService.createTaskQuery().executionId(executionId).list();
for(Task task : tasks)
{
resultList.add(typeConverter.convert(task));
}
resultList.add(typeConverter.getVirtualStartTask(processInstanceId, true));
}
else
{
resultList.add(typeConverter.getVirtualStartTask(executionId, true));
List<Task> tasks = taskService.createTaskQuery().executionId(executionId).list();
for (Task task : tasks)
{
resultList.add(typeConverter.convert(task));
}
}
return resultList;
}
@@ -1017,7 +1015,9 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
}
else
{
return typeConverter.convert((Execution)instance);
WorkflowPath path = typeConverter.convert((Execution)instance);
endStartTaskAutomatically(path, instance);
return path;
}
}
catch (ActivitiException ae)
@@ -1027,6 +1027,23 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
}
}
/**
* @param path
* @param instance
*/
private void endStartTaskAutomatically(WorkflowPath path, ProcessInstance instance)
{
// Check if StartTask Needs to be ended automatically
WorkflowDefinition definition = path.getInstance().getDefinition();
TypeDefinition metadata = definition.getStartTaskDefinition().getMetadata();
Set<QName> aspects = metadata.getDefaultAspectNames();
if(aspects.contains(WorkflowModel.ASPECT_END_AUTOMATICALLY))
{
String taskId = ActivitiConstants.START_TASK_PREFIX + instance.getId();
endStartTask(taskId);
}
}
/**
* {@inheritDoc}
*/
@@ -1296,7 +1313,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
// Check if the task is a virtual start task
if(localTaskId.startsWith(ActivitiConstants.START_TASK_PREFIX))
{
return endStartTask(taskId, localTaskId, transition);
return endStartTask(localTaskId);
}
return endNormalTask(taskId, localTaskId, transition);
@@ -1356,11 +1373,15 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
propertyConverter.updateTask(task, updates, null, null);
}
private WorkflowTask endStartTask(String taskId, String localTaskId, String transition)
private WorkflowTask endStartTask(String localTaskId)
{
// We don't end a task, we set a variable on the process-instance
// to indicate that it's started
String processInstanceId = localTaskId.replace(ActivitiConstants.START_TASK_PREFIX, "");
if(false == typeConverter.isStartTaskActive(processInstanceId))
{
return typeConverter.getVirtualStartTask(processInstanceId, false);
}
// Set start task end date on the process
runtimeService.setVariable(processInstanceId, ActivitiConstants.PROP_START_TASK_END_DATE, new Date());
@@ -1379,23 +1400,11 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
runtimeService.signal(processInstanceId);
// It's possible the process has ended after signalling the receive task
processInstance = activitiUtil.getProcessInstance(processInstanceId);
if (processInstance != null)
{
}
// Return virtual start task for the execution, it's safe to use the
// processInstanceId
return typeConverter.getVirtualStartTask(processInstanceId, false);
}
else
{
return typeConverter.getVirtualStartTask(activitiUtil.getHistoricProcessInstance(processInstanceId));
}
}
else
{
// Return virtual start task for the execution, it's safe to use the processInstanceId
return typeConverter.getVirtualStartTask(processInstanceId, false);
}
}
/**
* {@inheritDoc}
@@ -1515,7 +1524,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
if(localId.startsWith(ActivitiConstants.START_TASK_PREFIX))
{
String processInstanceId = localId.replace(ActivitiConstants.START_TASK_PREFIX ,"");
return getVirtualStartTaskForProcessInstance(processInstanceId);
return typeConverter.getVirtualStartTask(processInstanceId, null);
}
else
{
@@ -1884,8 +1893,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
// Only return start-task when a process or task id is set
if(processInstanceId != null)
{
// Extract processInstanceId
WorkflowTask workflowTask = getVirtualStartTaskForProcessInstance(processInstanceId);
WorkflowTask workflowTask = typeConverter.getVirtualStartTask(processInstanceId, null);
if(workflowTask != null)
{
boolean startTaskMatches = isStartTaskMatching(workflowTask, query);
@@ -2033,32 +2041,7 @@ public class ActivitiWorkflowEngine extends BPMEngine implements WorkflowEngine
public WorkflowTask getStartTask(String workflowInstanceId)
{
String instanceId = createLocalId(workflowInstanceId);
return getVirtualStartTaskForProcessInstance(instanceId);
}
public WorkflowTask getVirtualStartTaskForProcessInstance(String processInstanceId)
{
ProcessInstance runningInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if(runningInstance != null)
{
// Check the process instance variable to see if start-task has been completed
Date startTaskEndDate = (Date) runtimeService.getVariable(runningInstance.getProcessInstanceId(),
ActivitiConstants.PROP_START_TASK_END_DATE);
boolean startTaskEnded = (startTaskEndDate != null);
return typeConverter.getVirtualStartTask(runningInstance.getId(), !startTaskEnded);
}
else
{
HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
return typeConverter.getVirtualStartTask(hpi);
}
return typeConverter.getVirtualStartTask(instanceId, null);
}
/**

View File

@@ -26,6 +26,7 @@ import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.repo.workflow.DefaultWorkflowPropertyHandler;
import org.alfresco.repo.workflow.WorkflowAuthorityManager;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.WorkflowObjectFactory;
import org.alfresco.repo.workflow.WorkflowPropertyHandlerRegistry;
import org.alfresco.repo.workflow.WorkflowQNameConverter;
@@ -37,6 +38,7 @@ import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.springframework.beans.factory.FactoryBean;
/**
@@ -90,7 +92,8 @@ public class ActivitiWorkflowManagerFactory implements FactoryBean<ActivitiWorkf
WorkflowPropertyHandlerRegistry handlerRegistry = new WorkflowPropertyHandlerRegistry(defaultPropertyHandler, qNameConverter);
WorkflowAuthorityManager authorityManager = new WorkflowAuthorityManager(authorityDAO);
WorkflowObjectFactory factory = new WorkflowObjectFactory(qNameConverter, tenantService, messageService, dictionaryService, engineId);
QName defaultStartTaskType = WorkflowModel.TYPE_ACTIVTI_START_TASK;
WorkflowObjectFactory factory = new WorkflowObjectFactory(qNameConverter, tenantService, messageService, dictionaryService, engineId, defaultStartTaskType);
ActivitiUtil activitiUtil = new ActivitiUtil(processEngine);
ActivitiPropertyConverter propertyConverter = new ActivitiPropertyConverter(activitiUtil, factory, handlerRegistry, authorityManager, messageService, nodeConverter);
ActivitiTypeConverter typeConverter = new ActivitiTypeConverter(processEngine, factory, propertyConverter);

View File

@@ -81,6 +81,26 @@ public class ActivitiWorkflowServiceIntegrationTest extends AbstractWorkflowServ
assertEquals("Approve", outcome);
}
public void testStartTaskEndsAutomatically()
{
// Deploy the test workflow definition which uses the
// default Start Task type, so it should end automatically.
WorkflowDefinition definition = deployDefinition(getTestDefinitionPath());
// Start the Workflow
WorkflowPath path = workflowService.startWorkflow(definition.getId(), null);
String instanceId = path.getInstance().getId();
// Check the Start Task is completed.
WorkflowTask startTask = workflowService.getStartTask(instanceId);
assertEquals(WorkflowTaskState.COMPLETED, startTask.getState());
List<WorkflowTask> tasks = workflowService.getTasksForWorkflowPath(path.getId());
assertEquals(1, tasks.size());
String taskName = tasks.get(0).getName();
assertEquals("bpm_foo_task", taskName);
}
@Override
protected void checkTaskQueryStartTaskCompleted(String workflowInstanceId, WorkflowTask startTask)
{

View File

@@ -852,12 +852,10 @@ public class ActivitiPropertyConverter
*/
private List<QName> getMissingMandatoryTaskProperties(DelegateTask task)
{
TypeDefinition typeDefinition = typeManager.getFullTaskDefinition(task);
// retrieve properties of task
Map<QName, Serializable> existingValues = getTaskProperties(task, typeDefinition, false);
// retrieve definition of task
Map<QName, PropertyDefinition> propertyDefs = typeDefinition.getProperties();
Map<QName, AssociationDefinition> assocDefs = typeDefinition.getAssociations();

View File

@@ -3426,4 +3426,10 @@ public class JBPMEngine extends AlfrescoBpmEngine implements WorkflowEngine
}
}
@Override
protected QName getDefaultStartTaskType()
{
return WorkflowModel.TYPE_START_TASK;
}
}