Workflow Checkpoint:

- BPM Model (xml & QName definitions)
- Getting / Setting / Updating Task Properties

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3470 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Caruana
2006-08-08 17:35:50 +00:00
parent 499367c4e9
commit 47833426ca
9 changed files with 891 additions and 59 deletions

View File

@@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<model name="bpm:businessprocessmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<description>Business Process Model</description>
<author>Alfresco</author>
<version>1.0</version>
<!-- Imports are required to allow references to definitions in other models -->
<imports>
<!-- Import Alfresco Dictionary Definitions -->
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<!-- Import Alfresco Content Domain Model Definitions -->
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</imports>
<namespaces>
<namespace uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm"/>
</namespaces>
<constraints>
<constraint name="bpm:allowedPriority" type="LIST">
<parameter name="allowedValues">
<!-- TODO: Determine if priority values can be mapped to human-readable strings -->
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</parameter>
</constraint>
<constraint name="bpm:allowedStatus" type="LIST">
<parameter name="allowedValues">
<!-- TODO: Determine if status values can be mapped to human-readable strings -->
<list>
<value>Not Yet Started</value>
<value>In Progress</value>
<value>On Hold</value>
<value>Cancelled</value>
<value>Completed</value>
</list>
</parameter>
</constraint>
<constraint name="bpm:percentage" type="MINMAX">
<parameter name="minValue"><value>0</value></parameter>
<parameter name="maxValue"><value>100</value></parameter>
</constraint>
</constraints>
<types>
<!-- -->
<!-- Base definition for all Tasks -->
<!-- -->
<type name="bpm:task">
<title>Task</title>
<description>Task</description>
<parent>cm:content</parent>
<properties>
<!-- -->
<!-- Assignment -->
<!-- -->
<!-- Note: Implemented via cm:ownable aspect -->
<!-- -->
<!-- Task Identifier -->
<!-- -->
<property name="bpm:taskId">
<title>Task Identifier</title>
<type>d:long</type>
<protected>true</protected>
</property>
<!-- -->
<!-- Task Dates -->
<!-- -->
<property name="bpm:startDate">
<title>Start Date</title>
<type>d:date</type>
<protected>true</protected>
</property>
<property name="bpm:completionDate">
<title>End Date</title>
<type>d:date</type>
<protected>true</protected>
</property>
<property name="bpm:dueDate">
<title>Due Date</title>
<type>d:date</type>
</property>
<!-- -->
<!-- Task Progress -->
<!-- -->
<property name="bpm:status">
<title>Status</title>
<type>d:text</type>
<protected>true</protected>
<constraints>
<constraint ref="bpm:allowedStatus"/>
</constraints>
</property>
<property name="bpm:priority">
<title>Priority</title>
<type>d:int</type>
<constraints>
<constraint ref="bpm:allowedPriority"/>
</constraints>
</property>
<property name="bpm:percentComplete">
<title>Percentage Complete</title>
<type>d:int</type>
<constraints>
<constraint ref="bpm:percentage"/>
</constraints>
</property>
<!-- -->
<!-- Task Notes -->
<!-- -->
<!-- Note: Implemented via cm:content property : see 'fm:post' -->
</properties>
<associations>
<!-- List of users who may potentially get assigned to the task -->
<association name="bpm:pooledActors">
<title>Pooled Users</title>
<source>
<mandatory>false</mandatory>
<many>false</many>
</source>
<target>
<class>cm:person</class>
<mandatory>false</mandatory>
<many>true</many>
</target>
</association>
</associations>
<mandatory-aspects>
<aspect>cm:ownable</aspect>
</mandatory-aspects>
</type>
<!-- -->
<!-- The base for all Ad-hoc Tasks as created by Users -->
<!-- -->
<type name="bpm:adhocTask">
<title>Ad-hoc Task</title>
<description>Task assigned by User</description>
<parent>bpm:task</parent>
<mandatory-aspects>
<aspect>cm:attachable</aspect>
</mandatory-aspects>
</type>
<!-- -->
<!-- The base for all Tasks assigned via a Workflow -->
<!-- -->
<type name="bpm:workflowTask">
<title>Workflow Task</title>
<description>Task assigned via Workflow</description>
<parent>bpm:task</parent>
<properties>
<!-- -->
<!-- Task Context -->
<!-- e.g. Space, Document -->
<!-- -->
<property name="bpm:context">
<title>Task Context</title>
<type>d:noderef</type>
</property>
</properties>
<associations>
<association name="bpm:package">
<title>Workflow Package</title>
<source>
<mandatory>false</mandatory>
<many>false</many>
</source>
<target>
<class>bpm:workflowPackage</class>
<mandatory>true</mandatory>
<many>false</many>
</target>
</association>
</associations>
</type>
<!-- -->
<!-- An Alfresco Space for managing ad-hoc Tasks -->
<!-- -->
<type name="bpm:taskspace">
<title>Task Space</title>
<parent>cm:folder</parent>
</type>
</types>
<aspects>
<!-- -->
<!-- A collection of content routed through a workflow. -->
<!-- -->
<!-- Note: this aspect should be applied to a container such as -->
<!-- a folder, versioned folder or layered folder. -->
<!-- -->
<aspect name="bpm:workflowPackage">
<title>Workflow Package</title>
<properties>
<!-- Items within package that have been marked as complete -->
<property name="bpm:completedItems">
<type>d:noderef</type>
<multiple>true</multiple>
</property>
<!-- TODO: Define properties (replicated from Workflow/Task Engine) for -->
<!-- search within Alfresco e.g. task info, workflow info ... -->
</properties>
<!-- Commentary -->
<!-- Note: Implement tracking of comments via fm:discussable aspect -->
<!-- Note: Haven't made this aspect mandatory as this would force creation -->
<!-- of forum folder against each package, even if there aren't any -->
<!-- comments -->
</aspect>
<!-- Capability for adding a task management space to any entity in the system, -->
<!-- in particular, folders and content -->
<aspect name="bpm:tasks">
<associations>
<child-association name="bpm:tasks">
<source>
<mandatory>false</mandatory>
<many>false</many>
</source>
<target>
<class>bpm:taskspace</class>
<mandatory>true</mandatory>
<many>false</many>
</target>
<duplicate>false</duplicate>
</child-association>
</associations>
</aspect>
</aspects>
</model>

View File

@@ -3,6 +3,18 @@
<beans> <beans>
<!-- -->
<!-- Workflow Model -->
<!-- -->
<bean id="workflow.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
<property name="models">
<list>
<value>alfresco/model/bpmModel.xml</value>
</list>
</property>
</bean>
<!-- --> <!-- -->
<!-- Workflow Service Implementation --> <!-- Workflow Service Implementation -->
<!-- --> <!-- -->

View File

@@ -70,6 +70,25 @@ public interface TaskComponent
*/ */
public WorkflowTask updateTask(String taskId, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add, Map<QName, List<NodeRef>> remove); public WorkflowTask updateTask(String taskId, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add, Map<QName, List<NodeRef>> remove);
/**
* Start the specified Task
*
* Note: this is an optional task operation. It may be used to track
* when work started on a task as well as resume a suspended task.
*
* @param taskId the task to start
* @return the updated task
*/
public WorkflowTask startTask(String taskId);
/**
* Suspend the specified Task
*
* @param taskId
* @return the update task
*/
public WorkflowTask suspendTask(String taskId);
/** /**
* End the Task (i.e. complete the task) * End the Task (i.e. complete the task)
* *

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.repo.workflow;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
/**
* Workflow Model Constants
*/
public interface WorkflowModel
{
// task constants
static final QName TYPE_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "task");
static final QName PROP_TASK_ID = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "taskId");
static final QName PROP_START_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "startDate");
static final QName PROP_DUE_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "dueDate");
static final QName PROP_COMPLETION_DATE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "completionDate");
static final QName PROP_PRIORITY = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "priority");
static final QName PROP_STATUS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "status");
static final QName PROP_PERCENT_COMPLETE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "percentComplete");
static final QName ASSOC_POOLED_ACTORS = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "pooledActors");
// workflow task contstants
static final QName TYPE_WORKFLOW_TASK = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "workflowTask");
static final QName PROP_CONTEXT = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "context");
static final QName ASSOC_PACKAGE = QName.createQName(NamespaceService.BPM_MODEL_1_0_URI, "package");
}

View File

@@ -19,17 +19,28 @@ package org.alfresco.repo.workflow.jbpm;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.workflow.BPMEngine; import org.alfresco.repo.workflow.BPMEngine;
import org.alfresco.repo.workflow.TaskComponent; import org.alfresco.repo.workflow.TaskComponent;
import org.alfresco.repo.workflow.WorkflowComponent; import org.alfresco.repo.workflow.WorkflowComponent;
import org.alfresco.repo.workflow.WorkflowDefinitionComponent; import org.alfresco.repo.workflow.WorkflowDefinitionComponent;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.dictionary.AspectDefinition;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowInstance;
@@ -43,7 +54,6 @@ import org.alfresco.service.namespace.QName;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import org.jbpm.JbpmContext; import org.jbpm.JbpmContext;
import org.jbpm.JbpmException; import org.jbpm.JbpmException;
import org.jbpm.context.exe.ContextInstance;
import org.jbpm.db.GraphSession; import org.jbpm.db.GraphSession;
import org.jbpm.db.TaskMgmtSession; import org.jbpm.db.TaskMgmtSession;
import org.jbpm.graph.def.Node; import org.jbpm.graph.def.Node;
@@ -72,6 +82,8 @@ public class JBPMEngine extends BPMEngine
// Implementation dependencies // Implementation dependencies
protected DictionaryService dictionaryService; protected DictionaryService dictionaryService;
protected NamespaceService namespaceService; protected NamespaceService namespaceService;
protected NodeService nodeService;
protected PersonService personService;
private JbpmTemplate jbpmTemplate; private JbpmTemplate jbpmTemplate;
/** /**
@@ -104,6 +116,26 @@ public class JBPMEngine extends BPMEngine
this.namespaceService = namespaceService; this.namespaceService = namespaceService;
} }
/**
* Sets the Node Service
*
* @param nodeService
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Sets the Person Service
*
* @param personService
*/
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
// //
// Workflow Definition... // Workflow Definition...
@@ -114,8 +146,8 @@ public class JBPMEngine extends BPMEngine
*/ */
public WorkflowDefinition deployDefinition(InputStream workflowDefinition) public WorkflowDefinition deployDefinition(InputStream workflowDefinition)
{ {
// TODO Auto-generated method stub // TODO
return null; throw new UnsupportedOperationException();
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -123,8 +155,8 @@ public class JBPMEngine extends BPMEngine
*/ */
public void undeployDefinition(String workflowDefinitionId) public void undeployDefinition(String workflowDefinitionId)
{ {
// TODO Auto-generated method stub // TODO
throw new UnsupportedOperationException();
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -465,10 +497,121 @@ public class JBPMEngine extends BPMEngine
/* (non-Javadoc) /* (non-Javadoc)
* @see org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, java.util.Map, java.util.Map, java.util.Map) * @see org.alfresco.repo.workflow.TaskComponent#updateTask(java.lang.String, java.util.Map, java.util.Map, java.util.Map)
*/ */
public WorkflowTask updateTask(String taskId, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add, Map<QName, List<NodeRef>> remove) public WorkflowTask updateTask(final String taskId, final Map<QName, Serializable> properties, final Map<QName, List<NodeRef>> add, final Map<QName, List<NodeRef>> remove)
{ {
// TODO try
throw new UnsupportedOperationException(); {
return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
// retrieve task
TaskMgmtSession taskSession = context.getTaskMgmtSession();
TaskInstance taskInstance = taskSession.loadTaskInstance(getJbpmId(taskId));
// create properties to set on task instance
Map<QName, Serializable> newProperties = properties;
if (newProperties == null && (add != null || remove != null))
{
newProperties = new HashMap<QName, Serializable>(10);
}
if (add != null || remove != null)
{
Map<QName, Serializable> existingProperties = getTaskProperties(taskInstance);
if (add != null)
{
// add new associations
for (Entry<QName, List<NodeRef>> toAdd : add.entrySet())
{
// retrieve existing list of noderefs for association
List<NodeRef> existingAdd = (List<NodeRef>)newProperties.get(toAdd.getKey());
if (existingAdd == null)
{
existingAdd = (List<NodeRef>)existingProperties.get(toAdd.getKey());
}
// make the additions
if (existingAdd == null)
{
newProperties.put(toAdd.getKey(), (Serializable)toAdd.getValue());
}
else
{
for (NodeRef nodeRef : (List<NodeRef>)toAdd.getValue())
{
if (!(existingAdd.contains(nodeRef)))
{
existingAdd.add(nodeRef);
}
}
}
}
}
if (remove != null)
{
// add new associations
for (Entry<QName, List<NodeRef>> toRemove: remove.entrySet())
{
// retrieve existing list of noderefs for association
List<NodeRef> existingRemove = (List<NodeRef>)newProperties.get(toRemove.getKey());
if (existingRemove == null)
{
existingRemove = (List<NodeRef>)existingProperties.get(toRemove.getKey());
}
// make the subtractions
if (existingRemove != null)
{
for (NodeRef nodeRef : (List<NodeRef>)toRemove.getValue())
{
existingRemove.remove(nodeRef);
}
}
}
}
}
// update the task
if (newProperties != null)
{
setTaskProperties(taskInstance, newProperties);
// save
ProcessInstance processInstance = taskInstance.getToken().getProcessInstance();
context.save(processInstance);
}
// note: the ending of a task may not have signalled (i.e. more than one task exists at
// this node)
return createWorkflowTask(taskInstance);
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to update workflow task '" + taskId + "'", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.TaskComponent#startTask(java.lang.String)
*/
public WorkflowTask startTask(String taskId)
{
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.TaskComponent#suspendTask(java.lang.String)
*/
public WorkflowTask suspendTask(String taskId)
{
// TODO Auto-generated method stub
return null;
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -526,45 +669,51 @@ public class JBPMEngine extends BPMEngine
return null; return null;
} }
/**
* Sets Properties of Task
*
* @param instance task instance
* @param properties properties to set
*/
protected void setTaskProperties(TaskInstance instance, Map<QName, Serializable> properties)
{
if (properties == null)
{
return;
}
// TODO: Use Dictionary to drive mapping
// TODO: Determine if NodeRefs and collection of NodeRefs need to be converted to String
ContextInstance context = instance.getContextInstance();
for (Entry<QName, Serializable> entry : properties.entrySet())
{
String name = null;
QName qname = entry.getKey();
if (qname.getNamespaceURI().equals(NamespaceService.DEFAULT_URI))
{
name = qname.getLocalName();
}
else
{
name = qname.toPrefixString(namespaceService);
}
context.setVariable(name, entry.getValue());
}
}
// //
// Helpers... // Helpers...
// //
/**
* Gets the Task definition of the specified Task
*
* @param task the task
* @return the task definition
*/
private TypeDefinition getTaskDefinition(Task task)
{
// TODO: Extend jBPM task instance to include dictionary definition qname?
QName typeName = QName.createQName(task.getName(), namespaceService);
TypeDefinition typeDef = dictionaryService.getType(typeName);
if (typeDef == null)
{
typeDef = dictionaryService.getType(WorkflowModel.TYPE_WORKFLOW_TASK);
if (typeDef == null)
{
throw new WorkflowException("Failed to find type definition '" + WorkflowModel.TYPE_WORKFLOW_TASK + "'");
}
}
return typeDef;
}
/**
* Convert the specified Type definition to an anonymous Type definition.
*
* This collapses all mandatory aspects into a single Type definition.
*
* @param typeDef the type definition
* @return the anonymous type definition
*/
private TypeDefinition getAnonymousTaskDefinition(TypeDefinition typeDef)
{
List<AspectDefinition> aspects = typeDef.getDefaultAspects();
List<QName> aspectNames = new ArrayList<QName>(aspects.size());
for (AspectDefinition aspect : aspects)
{
aspectNames.add(aspect.getName());
}
return dictionaryService.getAnonymousType(typeDef.getName(), aspectNames);
}
/** /**
* Get JBoss JBPM Id from Engine Global Id * Get JBoss JBPM Id from Engine Global Id
@@ -612,6 +761,214 @@ public class JBPMEngine extends BPMEngine
return token; return token;
} }
/**
* Sets Properties of Task
*
* @param instance task instance
* @param properties properties to set
*/
@SuppressWarnings("unchecked")
protected Map<QName, Serializable> getTaskProperties(TaskInstance instance)
{
// establish task definition
TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask()));
Map<QName, AssociationDefinition> taskAssocs = taskDef.getAssociations();
// map arbitrary task variables
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(10);
Map<String, Object> vars = instance.getVariablesLocally();
for (Entry<String, Object> entry : vars.entrySet())
{
String key = entry.getKey();
Object value = entry.getValue();
// perform data conversions
// NOTE: Only convert Authority name to NodeRef for now
QName qname = QName.createQName(key);
AssociationDefinition assocDef = taskAssocs.get(qname);
if (assocDef != null && assocDef.getTargetClass().equals(ContentModel.TYPE_PERSON))
{
// TODO: Also support group authorities
if (!(value instanceof String[]))
{
throw new WorkflowException("Task variable '" + qname + "' value is invalid format");
}
value = mapNameToAuthority((String[])value);
}
// place task variable in map to return
properties.put(qname, (Serializable)value);
}
// map jBPM task instance fields to properties
properties.put(WorkflowModel.PROP_TASK_ID, instance.getId());
properties.put(WorkflowModel.PROP_START_DATE, instance.getStart());
properties.put(WorkflowModel.PROP_DUE_DATE, instance.getDueDate());
properties.put(WorkflowModel.PROP_COMPLETION_DATE, instance.getEnd());
properties.put(WorkflowModel.PROP_PRIORITY, instance.getPriority());
properties.put(ContentModel.PROP_OWNER, instance.getActorId());
// map jBPM task instance collections to associations
Set pooledActors = instance.getPooledActors();
if (pooledActors != null)
{
String[] pooledActorIds = new String[pooledActors.size()];
pooledActors.toArray(pooledActorIds);
List<NodeRef> pooledActorNodeRefs = mapNameToAuthority(pooledActorIds);
properties.put(WorkflowModel.ASSOC_POOLED_ACTORS, (Serializable)pooledActorNodeRefs);
}
return properties;
}
/**
* Sets Properties of Task
*
* @param instance task instance
* @param properties properties to set
*/
protected void setTaskProperties(TaskInstance instance, Map<QName, Serializable> properties)
{
if (properties == null)
{
return;
}
// establish task definition
TypeDefinition taskDef = getAnonymousTaskDefinition(getTaskDefinition(instance.getTask()));
Map<QName, PropertyDefinition> taskProperties = taskDef.getProperties();
Map<QName, AssociationDefinition> taskAssocs = taskDef.getAssociations();
// map each parameter to task
for (Entry<QName, Serializable> entry : properties.entrySet())
{
QName key = entry.getKey();
Serializable value = entry.getValue();
// determine if writing property
// NOTE: some properties map to fields on jBPM task instance whilst
// others are set in the general variable bag on the task
PropertyDefinition propDef = taskProperties.get(key);
if (propDef != null)
{
if (propDef.isProtected())
{
// NOTE: only write non-protected properties
continue;
}
// map property to specific jBPM task instance field
if (key.equals(WorkflowModel.PROP_DUE_DATE))
{
if (!(value instanceof Date))
{
throw new WorkflowException("Task due date '" + value + "' is invalid");
}
instance.setDueDate((Date)value);
continue;
}
else if (key.equals(WorkflowModel.PROP_PRIORITY))
{
if (!(value instanceof Integer))
{
throw new WorkflowException("Task priority '" + value + "' is invalid");
}
instance.setPriority((Integer)value);
continue;
}
else if (key.equals(ContentModel.PROP_OWNER))
{
if (!(value instanceof String))
{
throw new WorkflowException("Task owner '" + value + "' is invalid");
}
instance.setActorId((String)value);
continue;
}
}
else
{
// determine if writing association
AssociationDefinition assocDef = taskAssocs.get(key);
if (assocDef != null)
{
// if association is to people, map them to authority names
// TODO: support group authorities
if (assocDef.getTargetClass().equals(ContentModel.TYPE_PERSON))
{
value = mapAuthorityToName((List<NodeRef>)value);
}
// map association to specific jBPM task instance field
if (key.equals(WorkflowModel.ASSOC_POOLED_ACTORS))
{
instance.setPooledActors((String[])value);
continue;
}
}
}
// no specific mapping to jBPM task has been established, so place into
// the generic task variable bag
String name = null;
if (key.getNamespaceURI().equals(NamespaceService.DEFAULT_URI))
{
name = key.getLocalName();
}
else
{
name = key.toString();
}
instance.setVariableLocally(name, value);
}
}
/**
* Convert a list of Alfresco Authorities to a list of authority Names
*
* @param authorities the authorities to convert
* @return the authority names
*/
private String[] mapAuthorityToName(List<NodeRef> authorities)
{
String[] names = null;
if (authorities != null)
{
names = new String[authorities.size()];
int i = 0;
for (NodeRef person : authorities)
{
String name = (String)nodeService.getProperty(person, ContentModel.PROP_USERNAME);
names[i++] = name;
}
}
return names;
}
/**
* Convert a list of authority Names to Alfresco Authorities
*
* @param names the authority names to convert
* @return the Alfresco authorities
*/
private List<NodeRef> mapNameToAuthority(String[] names)
{
List<NodeRef> authorities = null;
if (names != null)
{
authorities = new ArrayList<NodeRef>(names.length);
for (String name : names)
{
// TODO: Should this be an exception?
if (personService.personExists(name))
{
authorities.add(personService.getPerson(name));
}
}
}
return authorities;
}
// //
// Workflow Data Object Creation... // Workflow Data Object Creation...
@@ -714,9 +1071,7 @@ public class JBPMEngine extends BPMEngine
workflowTask.path = createWorkflowPath(task.getToken()); workflowTask.path = createWorkflowPath(task.getToken());
workflowTask.state = getWorkflowTaskState(task); workflowTask.state = getWorkflowTaskState(task);
workflowTask.definition = createWorkflowTaskDefinition(task.getTask()); workflowTask.definition = createWorkflowTaskDefinition(task.getTask());
workflowTask.properties = getTaskProperties(task);
// TODO: Properties and Associations
return workflowTask; return workflowTask;
} }
@@ -728,11 +1083,9 @@ public class JBPMEngine extends BPMEngine
*/ */
protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task) protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task)
{ {
// TODO: Extend jBPM task instance to include dictionary definition qname
WorkflowTaskDefinition taskDef = new WorkflowTaskDefinition(); WorkflowTaskDefinition taskDef = new WorkflowTaskDefinition();
taskDef.id = task.getName(); taskDef.id = task.getName();
QName typeName = QName.createQName(taskDef.id, namespaceService); taskDef.metadata = getTaskDefinition(task);
taskDef.metadata = dictionaryService.getType(typeName);
return taskDef; return taskDef;
} }

View File

@@ -17,14 +17,19 @@
package org.alfresco.repo.workflow.jbpm; package org.alfresco.repo.workflow.jbpm;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.workflow.BPMEngineRegistry; import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.repo.workflow.TaskComponent; import org.alfresco.repo.workflow.TaskComponent;
import org.alfresco.repo.workflow.WorkflowComponent; import org.alfresco.repo.workflow.WorkflowComponent;
import org.alfresco.repo.workflow.WorkflowDefinitionComponent; import org.alfresco.repo.workflow.WorkflowDefinitionComponent;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowInstance;
@@ -55,6 +60,8 @@ public class JBPMEngineTest extends BaseSpringTest
workflowDefinitionComponent = registry.getWorkflowDefinitionComponent("jbpm"); workflowDefinitionComponent = registry.getWorkflowDefinitionComponent("jbpm");
workflowComponent = registry.getWorkflowComponent("jbpm"); workflowComponent = registry.getWorkflowComponent("jbpm");
taskComponent = registry.getTaskComponent("jbpm"); taskComponent = registry.getTaskComponent("jbpm");
} }
@@ -97,6 +104,121 @@ public class JBPMEngineTest extends BaseSpringTest
} }
public void testStartWorkflowParameters()
{
WorkflowDefinition workflowDef = getTestDefinition();
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
params.put(WorkflowModel.PROP_TASK_ID, 3); // protected - shouldn't be written
params.put(WorkflowModel.PROP_DUE_DATE, new Date()); // task instance field
params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field
params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable
params.put(QName.createQName("", "Message"), "Hello World"); // context variable outside of task definition
params.put(QName.createQName("", "Array"), new String[] { "one", "two" }); // context variable outside of task definition
params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); // context variable outside of task definition
params.put(ContentModel.PROP_OWNER, "admin"); // task assignment
WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params);
assertNotNull(path);
assertTrue(path.id.endsWith("::/"));
assertNotNull(path.node);
assertNotNull(path.instance);
assertEquals(workflowDef.id, path.instance.definition.id);
List<WorkflowTask> tasks1 = workflowComponent.getTasksForWorkflowPath(path.id);
assertNotNull(tasks1);
assertEquals(1, tasks1.size());
WorkflowTask task = tasks1.get(0);
assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID));
assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE));
assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY));
assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE));
assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER));
}
public void testUpdateTask()
{
WorkflowDefinition workflowDef = getTestDefinition();
Map<QName, Serializable> params = new HashMap<QName, Serializable>();
params.put(WorkflowModel.PROP_TASK_ID, 3); // protected - shouldn't be written
params.put(WorkflowModel.PROP_DUE_DATE, new Date()); // task instance field
params.put(WorkflowModel.PROP_PRIORITY, 1); // task instance field
params.put(WorkflowModel.PROP_PERCENT_COMPLETE, 10); // context variable
params.put(QName.createQName("", "Message"), "Hello World"); // context variable outside of task definition
params.put(QName.createQName("", "Array"), new String[] { "one", "two" }); // context variable outside of task definition
params.put(QName.createQName("", "NodeRef"), new NodeRef("workspace://1/1001")); // context variable outside of task definition
params.put(ContentModel.PROP_OWNER, "admin"); // task assignment
WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, params);
assertNotNull(path);
assertTrue(path.id.endsWith("::/"));
assertNotNull(path.node);
assertNotNull(path.instance);
assertEquals(workflowDef.id, path.instance.definition.id);
List<WorkflowTask> tasks1 = workflowComponent.getTasksForWorkflowPath(path.id);
assertNotNull(tasks1);
assertEquals(1, tasks1.size());
WorkflowTask task = tasks1.get(0);
assertTrue(task.properties.containsKey(WorkflowModel.PROP_TASK_ID));
assertTrue(task.properties.containsKey(WorkflowModel.PROP_DUE_DATE));
assertTrue(task.properties.containsKey(WorkflowModel.PROP_PRIORITY));
assertTrue(task.properties.containsKey(WorkflowModel.PROP_PERCENT_COMPLETE));
assertTrue(task.properties.containsKey(ContentModel.PROP_OWNER));
// update with null parameters
try
{
WorkflowTask taskU1 = taskComponent.updateTask(task.id, null, null, null);
assertNotNull(taskU1);
}
catch(Throwable e)
{
fail("Task update failed with null parameters");
}
// update property value
Map<QName, Serializable> updateProperties2 = new HashMap<QName, Serializable>();
updateProperties2.put(WorkflowModel.PROP_PERCENT_COMPLETE, 100);
WorkflowTask taskU2 = taskComponent.updateTask(task.id, updateProperties2, null, null);
assertEquals(100, taskU2.properties.get(WorkflowModel.PROP_PERCENT_COMPLETE));
// add to assocation
QName assocName = QName.createQName("", "TestAssoc");
List<NodeRef> toAdd = new ArrayList<NodeRef>();
toAdd.add(new NodeRef("workspace://1/1001"));
toAdd.add(new NodeRef("workspace://1/1002"));
toAdd.add(new NodeRef("workspace://1/1003"));
Map<QName, List<NodeRef>> addAssocs = new HashMap<QName, List<NodeRef>>();
addAssocs.put(assocName, toAdd);
WorkflowTask taskU3 = taskComponent.updateTask(task.id, null, addAssocs, null);
assertNotNull(taskU3.properties.get(assocName));
assertEquals(3, ((List<NodeRef>)taskU3.properties.get(assocName)).size());
// add to assocation again
List<NodeRef> toAddAgain = new ArrayList<NodeRef>();
toAddAgain.add(new NodeRef("workspace://1/1004"));
toAddAgain.add(new NodeRef("workspace://1/1005"));
Map<QName, List<NodeRef>> addAssocsAgain = new HashMap<QName, List<NodeRef>>();
addAssocsAgain.put(assocName, toAddAgain);
WorkflowTask taskU4 = taskComponent.updateTask(task.id, null, addAssocsAgain, null);
assertNotNull(taskU4.properties.get(assocName));
assertEquals(5, ((List<NodeRef>)taskU4.properties.get(assocName)).size());
// remove assocation
List<NodeRef> toRemove = new ArrayList<NodeRef>();
toRemove.add(new NodeRef("workspace://1/1002"));
toRemove.add(new NodeRef("workspace://1/1003"));
Map<QName, List<NodeRef>> removeAssocs = new HashMap<QName, List<NodeRef>>();
removeAssocs.put(assocName, toRemove);
WorkflowTask taskU5 = taskComponent.updateTask(task.id, null, null, removeAssocs);
assertNotNull(taskU5.properties.get(assocName));
assertEquals(3, ((List<NodeRef>)taskU5.properties.get(assocName)).size());
}
public void testGetWorkflowInstances() public void testGetWorkflowInstances()
{ {
WorkflowDefinition workflowDef = getTestDefinition(); WorkflowDefinition workflowDef = getTestDefinition();

View File

@@ -17,10 +17,8 @@
package org.alfresco.service.cmr.workflow; package org.alfresco.service.cmr.workflow;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -51,16 +49,13 @@ public class WorkflowTask
/** Task Properties as described by Task Definition */ /** Task Properties as described by Task Definition */
public Map<QName, Serializable> properties; public Map<QName, Serializable> properties;
/** Task Associations as described by Task Definition */
public Map<QName, List<NodeRef>> associations;
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */
public String toString() public String toString()
{ {
return "WorkflowTask[id=" + id + ",name=" + name + ",state=" + state + ",def=" + definition + ",path=" + path.toString() + "]"; String propCount = (properties == null) ? "null" : "" + properties.size();
return "WorkflowTask[id=" + id + ",name=" + name + ",state=" + state + ",props=" + propCount + ",def=" + definition + ",path=" + path.toString() + "]";
} }
} }

View File

@@ -66,6 +66,12 @@ public interface NamespaceService extends NamespacePrefixResolver
/** Application Model Prefix */ /** Application Model Prefix */
public static final String APP_MODEL_PREFIX = "app"; public static final String APP_MODEL_PREFIX = "app";
/** Business Process Model URI */
public static final String BPM_MODEL_1_0_URI = "http://www.alfresco.org/model/bpm/1.0";
/** Business Process Model Prefix */
public static final String BPM_MODEL_PREFIX = "bpm";
/** Alfresco View Namespace URI */ /** Alfresco View Namespace URI */
public static final String REPOSITORY_VIEW_1_0_URI = "http://www.alfresco.org/view/repository/1.0"; public static final String REPOSITORY_VIEW_1_0_URI = "http://www.alfresco.org/view/repository/1.0";