Workflow checkpoint:

- BPM Engine Registry & Plug-in SPIs
- First JBoss JBPM based Implementation of SPIs
- Workflow Service Implementation
- Tests

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3436 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Caruana
2006-07-28 22:58:02 +00:00
parent 0c35c285bd
commit 25123a4504
22 changed files with 2556 additions and 3 deletions

View File

@@ -44,6 +44,7 @@ import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.cmr.view.ExporterService;
import org.alfresco.service.cmr.view.ImporterService;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
@@ -313,4 +314,12 @@ public class ServiceDescriptorRegistry
{
return (ScriptService)getService(SCRIPT_SERVICE);
}
/* (non-Javadoc)
* @see org.alfresco.service.ServiceRegistry#getWorkflowService()
*/
public WorkflowService getWorkflowService()
{
return (WorkflowService)getService(WORKFLOW_SERVICE);
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.cmr.workflow.WorkflowException;
import org.springframework.beans.factory.InitializingBean;
/**
* Base functionality for a plug-in BPM Engine
*
* @author davidc
*/
public class BPMEngine implements InitializingBean
{
private BPMEngineRegistry registry;
private String engineId;
/**
* Sets the BPM Engine Registry
*
* @param registry the registry
*/
public void setBPMEngineRegistry(BPMEngineRegistry registry)
{
this.registry = registry;
}
/**
* Sets the BPM Engine Id
*
* @param engineId the id
*/
public void setEngineId(String engineId)
{
this.engineId = engineId;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception
{
if (engineId == null || engineId.length() == 0)
{
throw new WorkflowException("Engine Id not specified");
}
if (this instanceof WorkflowDefinitionComponent)
{
registry.registerWorkflowDefinitionComponent(engineId, (WorkflowDefinitionComponent)this);
}
if (this instanceof WorkflowComponent)
{
registry.registerWorkflowComponent(engineId, (WorkflowComponent)this);
}
if (this instanceof TaskComponent)
{
registry.registerTaskComponent(engineId, (TaskComponent)this);
}
}
/**
* Construct a global Id for use outside of the engine
*
* @param localId the local engine id
* @return the global id
*/
protected String createGlobalId(String localId)
{
return BPMEngineRegistry.createGlobalId(engineId, localId);
}
/**
* Construct a local Id from a global Id
*
* @param globalId the global id
* @return the local id
*/
protected String createLocalId(String globalId)
{
return BPMEngineRegistry.getLocalId(globalId);
}
}

View File

@@ -0,0 +1,234 @@
/*
* 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 java.util.HashMap;
import java.util.Map;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* BPM Engine Registry
*
* Responsible for managing the list of registered BPM Engines for the
* following components:
*
* - Workflow Definition Component
* - Workflow Component
* - Task Component
*
* @author davidc
*/
public class BPMEngineRegistry
{
/** ID seperator used in global Ids */
private static final String ID_SEPERATOR = "://";
/** Logging support */
private static Log logger = LogFactory.getLog("org.alfresco.repo.workflow");
private Map<String, WorkflowDefinitionComponent> workflowDefinitionComponents;
private Map<String, WorkflowComponent> workflowComponents;
private Map<String, TaskComponent> taskComponents;
/**
* Construct
*/
public BPMEngineRegistry()
{
workflowDefinitionComponents = new HashMap<String, WorkflowDefinitionComponent>();
workflowComponents = new HashMap<String, WorkflowComponent>();
taskComponents = new HashMap<String, TaskComponent>();
}
/**
* Register a BPM Engine Workflow Definition Component
*
* @param engineId engine id
* @param engine implementing engine
*/
public void registerWorkflowDefinitionComponent(String engineId, WorkflowDefinitionComponent engine)
{
if (workflowDefinitionComponents.containsKey(engineId))
{
throw new WorkflowException("Workflow Definition Component already registered for engine id '" + engineId + "'");
}
workflowDefinitionComponents.put(engineId, engine);
if (logger.isInfoEnabled())
logger.info("Registered Workflow Definition Component '" + engineId + "' (" + engine.getClass() + ")");
}
/**
* Gets all registered Workflow Definition Components
*
* @return array of engine ids
*/
public String[] getWorkflowDefinitionComponents()
{
return workflowDefinitionComponents.keySet().toArray(new String[workflowDefinitionComponents.keySet().size()]);
}
/**
* Gets a specific BPM Engine Workflow Definition Component
*
* @param engineId engine id
* @return the Workflow Definition Component
*/
public WorkflowDefinitionComponent getWorkflowDefinitionComponent(String engineId)
{
return workflowDefinitionComponents.get(engineId);
}
/**
* Register a BPM Engine Workflow Component
*
* @param engineId engine id
* @param engine implementing engine
*/
public void registerWorkflowComponent(String engineId, WorkflowComponent engine)
{
if (workflowComponents.containsKey(engineId))
{
throw new WorkflowException("Workflow Component already registered for engine id '" + engineId + "'");
}
workflowComponents.put(engineId, engine);
if (logger.isInfoEnabled())
logger.info("Registered Workflow Component '" + engineId + "' (" + engine.getClass() + ")");
}
/**
* Gets all registered Workflow Components
*
* @return array of engine ids
*/
public String[] getWorkflowComponents()
{
return workflowComponents.keySet().toArray(new String[workflowComponents.keySet().size()]);
}
/**
* Gets a specific BPM Engine Workflow Component
*
* @param engineId engine id
* @return the Workflow Component
*/
public WorkflowComponent getWorkflowComponent(String engineId)
{
return workflowComponents.get(engineId);
}
/**
* Register a BPM Engine Task Component
*
* @param engineId engine id
* @param engine implementing engine
*/
public void registerTaskComponent(String engineId, TaskComponent engine)
{
if (taskComponents.containsKey(engineId))
{
throw new WorkflowException("Task Component already registered for engine id '" + engineId + "'");
}
taskComponents.put(engineId, engine);
if (logger.isInfoEnabled())
logger.info("Registered Task Component '" + engineId + "' (" + engine.getClass() + ")");
}
/**
* Gets all registered Task Components
*
* @return array of engine ids
*/
public String[] getTaskComponents()
{
return taskComponents.keySet().toArray(new String[taskComponents.keySet().size()]);
}
/**
* Gets a specific BPM Engine Task Component
*
* @param engineId engine id
* @return the Workflow Component
*/
public TaskComponent getTaskComponent(String engineId)
{
return taskComponents.get(engineId);
}
//
// BPM Engine Id support
//
/**
* Construct a global Id
*
* @param engineId engine id
* @param localId engine local id
* @return the global id
*/
public static String createGlobalId(String engineId, String localId)
{
return engineId + ID_SEPERATOR + localId;
}
/**
* Break apart a global id into its engine and local ids
*
* @param globalId the global id
* @return array containing engine id and global id in that order
*/
public static String[] getGlobalIdParts(String globalId)
{
String[] parts = globalId.split(ID_SEPERATOR);
if (parts.length != 2)
{
throw new WorkflowException("Invalid Global Id '" + globalId + "'");
}
return parts;
}
/**
* Get the engine id from a global id
*
* @param globalId the global id
* @return the engine id
*/
public static String getEngineId(String globalId)
{
return getGlobalIdParts(globalId)[0];
}
/**
* Get the local id from a global id
*
* @param globalId the global id
* @return the local id
*/
public static String getLocalId(String globalId)
{
return getGlobalIdParts(globalId)[1];
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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 java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.QName;
/**
* SPI to be implemented by a BPM Engine that provides Task management.
*
* @author davidc
*/
public interface TaskComponent
{
/**
* Gets a Task by unique Id
*
* @param taskId the task id
* @return the task
*/
public WorkflowTask getTaskById(String taskId);
/**
* Gets all tasks assigned to the specified authority
*
* @param authority the authority
* @param state filter by specified workflow task state
* @return the list of assigned tasks
*/
public List<WorkflowTask> getAssignedTasks(String authority, WorkflowTaskState state);
/**
* Gets the pooled tasks available to the specified authority
*
* @param authority the authority
* @return the list of pooled tasks
*/
public List<WorkflowTask> getPooledTasks(List<String> authorities);
/**
* Update the Properties and Associations of a Task
*
* @param taskId the task id to update
* @param properties the map of properties to set on the task (or null, if none to set)
* @param add the map of items to associate with the task (or null, if none to add)
* @param remove the map of items to dis-associate with the task (or null, if none to remove)
* @return the update task
*/
public WorkflowTask updateTask(String taskId, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add, Map<QName, List<NodeRef>> remove);
/**
* End the Task (i.e. complete the task)
*
* @param taskId the task id to end
* @param transition the task transition to take on completion (or null, for the default transition)
* @return the updated task
*/
public WorkflowTask endTask(String taskId, String transition);
}

View File

@@ -0,0 +1,89 @@
/*
* 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 java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.QName;
/**
* SPI to be implemented by a BPM Engine that provides Workflow instance management.
*
* @author davidc
*/
public interface WorkflowComponent
{
/**
* Start a Workflow Instance
*
* @param workflowDefinitionId the workflow definition id
* @param parameters the initial set of parameters used to populate the "Start Task" properties
* @return the initial workflow path
*/
public WorkflowPath startWorkflow(String workflowDefinitionId, Map<QName, Serializable> parameters);
/**
* Gets all "in-flight" workflow instances of the specified Workflow Definition
*
* @param workflowDefinitionId the workflow definition id
* @return the list of "in-fligth" workflow instances
*/
public List<WorkflowInstance> getActiveWorkflows(String workflowDefinitionId);
/**
* Gets all Paths for the specified Workflow instance
*
* @param workflowId workflow instance id
* @return the list of workflow paths
*/
public List<WorkflowPath> getWorkflowPaths(String workflowId);
/**
* Cancel an "in-fligth" Workflow instance
*
* @param workflowId the workflow instance to cancel
* @return an updated representation of the workflow instance
*/
public WorkflowInstance cancelWorkflow(String workflowId);
/**
* Signal the transition from one Workflow Node to another within an "in-flight"
* process.
*
* @param pathId the workflow path to signal on
* @param transition the transition to follow (or null, for the default transition)
* @return the updated workflow path
*/
public WorkflowPath signal(String pathId, String transition);
/**
* Gets all Tasks associated with the specified path
*
* @param pathId the path id
* @return the list of associated tasks
*/
public List<WorkflowTask> getTasksForWorkflowPath(String pathId);
}

View File

@@ -0,0 +1,66 @@
/*
* 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 java.io.InputStream;
import java.util.List;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
/**
* SPI to be implemented by a BPM Engine that provides Workflow Definition management.
*
* @author davidc
*/
public interface WorkflowDefinitionComponent
{
/**
* Deploy a Workflow Definition
*
* @param workflowDefinition the content object containing the definition
* @return workflow definition
*/
public WorkflowDefinition deployDefinition(InputStream workflowDefinition);
/**
* Undeploy an exisiting Workflow Definition
*
* TODO: Determine behaviour when "in-flight" workflow instances exist
*
* @param workflowDefinitionId the id of the definition to undeploy
*/
public void undeployDefinition(String workflowDefinitionId);
/**
* Gets all deployed Workflow Definitions
*
* @return the deployed workflow definitions
*/
public List<WorkflowDefinition> getDefinitions();
/**
* Gets a Workflow Definition by unique Id
*
* @param workflowDefinitionId the workflow definition id
* @return the deployed workflow definition
*/
public WorkflowDefinition getDefinitionById(String workflowDefinitionId);
}

View File

@@ -0,0 +1,279 @@
/*
* 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 java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.QName;
/**
* Default Alfresco Workflow Service whose implementation is backed by registered
* BPM Engine plug-in components.
*
* @author davidc
*/
public class WorkflowServiceImpl implements WorkflowService
{
private BPMEngineRegistry registry;
/**
* Sets the BPM Engine Registry
*
* @param registry bpm engine registry
*/
public void setBPMEngineRegistry(BPMEngineRegistry registry)
{
this.registry = registry;
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#deployDefinition(org.alfresco.service.cmr.repository.NodeRef)
*/
public WorkflowDefinition deployDefinition(NodeRef definitionContent)
{
// TODO
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#undeployDefinition(java.lang.String)
*/
public void undeployDefinition(String processDefinitionId)
{
// TODO
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitions()
*/
public List<WorkflowDefinition> getDefinitions()
{
List<WorkflowDefinition> definitions = new ArrayList<WorkflowDefinition>(10);
String[] ids = registry.getWorkflowDefinitionComponents();
for (String id: ids)
{
WorkflowDefinitionComponent component = registry.getWorkflowDefinitionComponent(id);
definitions.addAll(component.getDefinitions());
}
return Collections.unmodifiableList(definitions);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitionById(java.lang.String)
*/
public WorkflowDefinition getDefinitionById(String workflowDefinitionId)
{
String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId);
WorkflowDefinitionComponent component = getWorkflowDefinitionComponent(engineId);
return component.getDefinitionById(workflowDefinitionId);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#startWorkflow(java.lang.String, java.util.Map)
*/
public WorkflowPath startWorkflow(String workflowDefinitionId, Map<QName, Serializable> parameters)
{
String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId);
WorkflowComponent component = getWorkflowComponent(engineId);
return component.startWorkflow(workflowDefinitionId, parameters);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#startWorkflowFromTemplate(org.alfresco.service.cmr.repository.NodeRef)
*/
public WorkflowPath startWorkflowFromTemplate(NodeRef templateDefinition)
{
// TODO
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getActiveWorkflows(java.lang.String)
*/
public List<WorkflowInstance> getActiveWorkflows(String workflowDefinitionId)
{
String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId);
WorkflowComponent component = getWorkflowComponent(engineId);
return component.getActiveWorkflows(workflowDefinitionId);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getWorkflowPaths(java.lang.String)
*/
public List<WorkflowPath> getWorkflowPaths(String workflowId)
{
String engineId = BPMEngineRegistry.getEngineId(workflowId);
WorkflowComponent component = getWorkflowComponent(engineId);
return component.getWorkflowPaths(workflowId);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#cancelWorkflow(java.lang.String)
*/
public WorkflowInstance cancelWorkflow(String workflowId)
{
String engineId = BPMEngineRegistry.getEngineId(workflowId);
WorkflowComponent component = getWorkflowComponent(engineId);
return component.cancelWorkflow(workflowId);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#signal(java.lang.String, java.lang.String)
*/
public WorkflowPath signal(String pathId, String transition)
{
String engineId = BPMEngineRegistry.getEngineId(pathId);
WorkflowComponent component = getWorkflowComponent(engineId);
return component.signal(pathId, transition);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getTasksForWorkflowPath(java.lang.String)
*/
public List<WorkflowTask> getTasksForWorkflowPath(String pathId)
{
String engineId = BPMEngineRegistry.getEngineId(pathId);
WorkflowComponent component = getWorkflowComponent(engineId);
return component.getTasksForWorkflowPath(pathId);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getAssignedTasks(java.lang.String, org.alfresco.service.cmr.workflow.WorkflowTaskState)
*/
public List<WorkflowTask> getAssignedTasks(String authority, WorkflowTaskState state)
{
List<WorkflowTask> tasks = new ArrayList<WorkflowTask>(10);
String[] ids = registry.getTaskComponents();
for (String id: ids)
{
TaskComponent component = registry.getTaskComponent(id);
tasks.addAll(component.getAssignedTasks(authority, state));
}
return Collections.unmodifiableList(tasks);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getPooledTasks(java.lang.String)
*/
public List<WorkflowTask> getPooledTasks(String authority)
{
// TODO: Expand authorities to include associated groups (and parent groups)
List<String> authorities = new ArrayList<String>();
authorities.add(authority);
List<WorkflowTask> tasks = new ArrayList<WorkflowTask>(10);
String[] ids = registry.getTaskComponents();
for (String id: ids)
{
TaskComponent component = registry.getTaskComponent(id);
tasks.addAll(component.getPooledTasks(authorities));
}
return Collections.unmodifiableList(tasks);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#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)
{
String engineId = BPMEngineRegistry.getEngineId(taskId);
TaskComponent component = getTaskComponent(engineId);
return component.updateTask(taskId, properties, add, remove);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#endTask(java.lang.String, java.lang.String)
*/
public WorkflowTask endTask(String taskId, String transition)
{
String engineId = BPMEngineRegistry.getEngineId(taskId);
TaskComponent component = getTaskComponent(engineId);
return component.endTask(taskId, transition);
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.workflow.WorkflowService#getTaskById(java.lang.String)
*/
public WorkflowTask getTaskById(String taskId)
{
// TODO
throw new UnsupportedOperationException();
}
/**
* Gets the Workflow Definition Component registered against the specified BPM Engine Id
*
* @param engineId engine id
*/
private WorkflowDefinitionComponent getWorkflowDefinitionComponent(String engineId)
{
WorkflowDefinitionComponent component = registry.getWorkflowDefinitionComponent(engineId);
if (component == null)
{
throw new WorkflowException("Workflow Definition Component for engine id '" + engineId + "' is not registered");
}
return component;
}
/**
* Gets the Workflow Component registered against the specified BPM Engine Id
*
* @param engineId engine id
*/
private WorkflowComponent getWorkflowComponent(String engineId)
{
WorkflowComponent component = registry.getWorkflowComponent(engineId);
if (component == null)
{
throw new WorkflowException("Workflow Component for engine id '" + engineId + "' is not registered");
}
return component;
}
/**
* Gets the Task Component registered against the specified BPM Engine Id
*
* @param engineId engine id
*/
private TaskComponent getTaskComponent(String engineId)
{
TaskComponent component = registry.getTaskComponent(engineId);
if (component == null)
{
throw new WorkflowException("Task Component for engine id '" + engineId + "' is not registered");
}
return component;
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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 java.util.List;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.util.BaseSpringTest;
/**
* Workflow Service Implementation Tests
*
* @author davidc
*/
public class WorkflowServiceImplTest extends BaseSpringTest
{
WorkflowService workflowService;
//@Override
protected void onSetUpInTransaction() throws Exception
{
workflowService = (WorkflowService)applicationContext.getBean(ServiceRegistry.WORKFLOW_SERVICE.getLocalName());
}
public void testGetWorkflowDefinitions()
{
List<WorkflowDefinition> workflowDefs = workflowService.getDefinitions();
assertNotNull(workflowDefs);
assertTrue(workflowDefs.size() > 0);
}
public void testStartWorkflow()
{
List<WorkflowDefinition> workflowDefs = workflowService.getDefinitions();
assertNotNull(workflowDefs);
assertTrue(workflowDefs.size() > 0);
WorkflowDefinition workflowDef = workflowDefs.get(0);
WorkflowPath path = workflowService.startWorkflow(workflowDef.id, null);
assertNotNull(path);
assertTrue(path.active);
assertNotNull(path.node);
assertNotNull(path.instance);
assertEquals(workflowDef.id, path.instance.definition.id);
}
}

View File

@@ -0,0 +1,757 @@
/*
* 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.jbpm;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.workflow.BPMEngine;
import org.alfresco.repo.workflow.TaskComponent;
import org.alfresco.repo.workflow.WorkflowComponent;
import org.alfresco.repo.workflow.WorkflowDefinitionComponent;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowNode;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.hibernate.proxy.HibernateProxy;
import org.jbpm.JbpmContext;
import org.jbpm.JbpmException;
import org.jbpm.context.exe.ContextInstance;
import org.jbpm.db.GraphSession;
import org.jbpm.db.TaskMgmtSession;
import org.jbpm.graph.def.Node;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.def.Transition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
import org.jbpm.taskmgmt.def.Task;
import org.jbpm.taskmgmt.exe.TaskInstance;
import org.springmodules.workflow.jbpm31.JbpmCallback;
import org.springmodules.workflow.jbpm31.JbpmTemplate;
/**
* JBoss JBPM based implementation of:
*
* Workflow Definition Component
* Workflow Component
* Task Component
*
* @author davidc
*/
public class JBPMEngine extends BPMEngine
implements WorkflowDefinitionComponent, WorkflowComponent, TaskComponent
{
// Implementation dependencies
protected DictionaryService dictionaryService;
protected NamespaceService namespaceService;
private JbpmTemplate jbpmTemplate;
/**
* Sets the JBPM Template used for accessing JBoss JBPM in the correct context
*
* @param jbpmTemplate
*/
public void setJBPMTemplate(JbpmTemplate jbpmTemplate)
{
this.jbpmTemplate = jbpmTemplate;
}
/**
* Sets the Dictionary Service
*
* @param dictionaryService
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* Sets the Namespace Service
*
* @param namespaceService
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
//
// Workflow Definition...
//
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#deployDefinition(java.io.InputStream)
*/
public WorkflowDefinition deployDefinition(InputStream workflowDefinition)
{
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#undeployDefinition(java.lang.String)
*/
public void undeployDefinition(String workflowDefinitionId)
{
// TODO Auto-generated method stub
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions()
*/
@SuppressWarnings("unchecked")
public List<WorkflowDefinition> getDefinitions()
{
try
{
return (List<WorkflowDefinition>)jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
GraphSession graphSession = context.getGraphSession();
List<ProcessDefinition> processDefs = (List<ProcessDefinition>)graphSession.findLatestProcessDefinitions();
List<WorkflowDefinition> workflowDefs = new ArrayList<WorkflowDefinition>(processDefs.size());
for (ProcessDefinition processDef : processDefs)
{
WorkflowDefinition workflowDef = createWorkflowDefinition(processDef);
workflowDefs.add(workflowDef);
}
return workflowDefs;
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to retrieve workflow definitions", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitionById(java.lang.String)
*/
public WorkflowDefinition getDefinitionById(String workflowDefinitionId)
{
// TODO
throw new UnsupportedOperationException();
}
//
// Workflow Instance Management...
//
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowComponent#startWorkflow(java.lang.String, java.util.Map)
*/
@SuppressWarnings("unchecked")
public WorkflowPath startWorkflow(final String workflowDefinitionId, final Map<QName, Serializable> parameters)
{
try
{
return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
// initialise jBPM actor (for any processes that wish to record the initiator)
context.setActorId(AuthenticationUtil.getCurrentUserName());
// construct a new process
GraphSession graphSession = context.getGraphSession();
ProcessDefinition processDefinition = graphSession.loadProcessDefinition(getJbpmId(workflowDefinitionId));
ProcessInstance processInstance = new ProcessInstance(processDefinition);
Token token = processInstance.getRootToken();
// create the start task if one exists
Task startTask = processInstance.getTaskMgmtInstance().getTaskMgmtDefinition().getStartTask();
if (startTask != null)
{
TaskInstance taskInstance = processInstance.getTaskMgmtInstance().createStartTaskInstance();
setTaskProperties(taskInstance, parameters);
token = taskInstance.getToken();
}
// Save the process instance along with the task instance
context.save(processInstance);
return createWorkflowPath(token);
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to start workflow " + workflowDefinitionId, e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowComponent#getActiveWorkflows(java.lang.String)
*/
@SuppressWarnings("unchecked")
public List<WorkflowInstance> getActiveWorkflows(final String workflowDefinitionId)
{
try
{
return (List<WorkflowInstance>) jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
GraphSession graphSession = context.getGraphSession();
List<ProcessInstance> processInstances = graphSession.findProcessInstances(getJbpmId(workflowDefinitionId));
List<WorkflowInstance> workflowInstances = new ArrayList<WorkflowInstance>(processInstances.size());
for (ProcessInstance processInstance : processInstances)
{
if (!processInstance.hasEnded())
{
WorkflowInstance workflowInstance = createWorkflowInstance(processInstance);
workflowInstances.add(workflowInstance);
}
}
return workflowInstances;
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to retrieve workflow instances for definition '" + workflowDefinitionId + "'", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowComponent#getWorkflowPaths(java.lang.String)
*/
@SuppressWarnings("unchecked")
public List<WorkflowPath> getWorkflowPaths(final String workflowId)
{
try
{
return (List<WorkflowPath>) jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
// retrieve process instance
GraphSession graphSession = context.getGraphSession();
ProcessInstance processInstance = graphSession.loadProcessInstance(getJbpmId(workflowId));
// convert jBPM tokens to workflow posisitons
List<Token> tokens = processInstance.findAllTokens();
List<WorkflowPath> paths = new ArrayList<WorkflowPath>(tokens.size());
for (Token token : tokens)
{
if (!token.hasEnded())
{
WorkflowPath path = createWorkflowPath(token);
paths.add(path);
}
}
return paths;
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to retrieve workflow paths for workflow instance '" + workflowId + "'", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang.String)
*/
public WorkflowInstance cancelWorkflow(final String workflowId)
{
try
{
return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
// retrieve and cancel process instance
GraphSession graphSession = context.getGraphSession();
ProcessInstance processInstance = graphSession.loadProcessInstance(getJbpmId(workflowId));
processInstance.end();
// save the process instance along with the task instance
context.save(processInstance);
return createWorkflowInstance(processInstance);
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to cancel workflow instance '" + workflowId + "'", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowComponent#signal(java.lang.String, java.lang.String)
*/
public WorkflowPath signal(final String pathId, final String transition)
{
try
{
return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
// retrieve jBPM token for workflow position
GraphSession graphSession = context.getGraphSession();
Token token = getWorkflowToken(graphSession, pathId);
// signal the transition
if (transition == null)
{
token.signal();
}
else
{
Node node = token.getNode();
if (!node.hasLeavingTransition(transition))
{
throw new WorkflowException("Transition '" + transition + "' is invalid for Workflow path '" + pathId + "'");
}
token.signal(transition);
}
// save
ProcessInstance processInstance = token.getProcessInstance();
context.save(processInstance);
// return new workflow path
return createWorkflowPath(token);
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to signal transition '" + transition + "' from workflow path '" + pathId + "'", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.WorkflowComponent#getTasksForWorkflowPath(java.lang.String)
*/
@SuppressWarnings("unchecked")
public List<WorkflowTask> getTasksForWorkflowPath(final String pathId)
{
try
{
return (List<WorkflowTask>) jbpmTemplate.execute(new JbpmCallback()
{
public List<WorkflowTask> doInJbpm(JbpmContext context)
{
// retrieve tasks at specified workflow path
GraphSession graphSession = context.getGraphSession();
Token token = getWorkflowToken(graphSession, pathId);
TaskMgmtSession taskSession = context.getTaskMgmtSession();
List<TaskInstance> tasks = taskSession.findTaskInstancesByToken(token.getId());
List<WorkflowTask> workflowTasks = new ArrayList<WorkflowTask>(tasks.size());
for (TaskInstance task : tasks)
{
WorkflowTask workflowTask = createWorkflowTask(task);
workflowTasks.add(workflowTask);
}
return workflowTasks;
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to retrieve tasks assigned at Workflow path '" + pathId + "'", e);
}
}
//
// Task Management ...
//
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.TaskComponent#getAssignedTasks(java.lang.String, org.alfresco.service.cmr.workflow.WorkflowTaskState)
*/
@SuppressWarnings("unchecked")
public List<WorkflowTask> getAssignedTasks(final String authority, final WorkflowTaskState state)
{
try
{
return (List<WorkflowTask>) jbpmTemplate.execute(new JbpmCallback()
{
public List<WorkflowTask> doInJbpm(JbpmContext context)
{
// retrieve tasks assigned to authority
TaskMgmtSession taskSession = context.getTaskMgmtSession();
List<TaskInstance> tasks = taskSession.findTaskInstances(authority);
List<WorkflowTask> workflowTasks = new ArrayList<WorkflowTask>(tasks.size());
for (TaskInstance task : tasks)
{
if (getWorkflowTaskState(task).equals(state))
{
WorkflowTask workflowTask = createWorkflowTask(task);
workflowTasks.add(workflowTask);
}
}
return workflowTasks;
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to retrieve tasks assigned to authority '" + authority + "'", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.TaskComponent#getPooledTasks(java.util.List)
*/
@SuppressWarnings("unchecked")
public List<WorkflowTask> getPooledTasks(final List<String> authorities)
{
try
{
return (List<WorkflowTask>) jbpmTemplate.execute(new JbpmCallback()
{
public List<WorkflowTask> doInJbpm(JbpmContext context)
{
// retrieve pooled tasks for specified authorities
TaskMgmtSession taskSession = context.getTaskMgmtSession();
List<TaskInstance> tasks = taskSession.findPooledTaskInstances(authorities);
List<WorkflowTask> workflowTasks = new ArrayList<WorkflowTask>(tasks.size());
for (TaskInstance task : tasks)
{
WorkflowTask workflowTask = createWorkflowTask(task);
workflowTasks.add(workflowTask);
}
return workflowTasks;
}
});
}
catch(JbpmException e)
{
throw new WorkflowException("Failed to retrieve pooled tasks for authorities '" + authorities + "'", e);
}
}
/* (non-Javadoc)
* @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)
{
// TODO
throw new UnsupportedOperationException();
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.TaskComponent#endTask(java.lang.String, java.lang.String)
*/
public WorkflowTask endTask(final String taskId, final String transition)
{
try
{
return (WorkflowTask) jbpmTemplate.execute(new JbpmCallback()
{
public Object doInJbpm(JbpmContext context)
{
// retrieve task
TaskMgmtSession taskSession = context.getTaskMgmtSession();
TaskInstance taskInstance = taskSession.loadTaskInstance(getJbpmId(taskId));
// signal the transition on the task
if (transition == null)
{
taskInstance.end();
}
else
{
Node node = taskInstance.getTask().getTaskNode();
if (node.getLeavingTransition(transition) == null)
{
throw new WorkflowException("Transition '" + transition + "' is invalid for Workflow task '" + taskId + "'");
}
taskInstance.end(transition);
}
// 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 signal transition '" + transition + "' from workflow task '" + taskId + "'", e);
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.workflow.TaskComponent#getTaskById(java.lang.String)
*/
public WorkflowTask getTaskById(String taskId)
{
// TODO Auto-generated method stub
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...
//
/**
* Get JBoss JBPM Id from Engine Global Id
*
* @param id global id
* @return JBoss JBPM Id
*/
protected long getJbpmId(String id)
{
try
{
String theLong = createLocalId(id);
return new Long(theLong);
}
catch(NumberFormatException e)
{
throw new WorkflowException("Format of id '" + id + "' is invalid", e);
}
}
/**
* Get the JBoss JBPM Token for the Workflow Path
*
* @param session JBoss JBPM Graph Session
* @param pathId workflow path id
* @return JBoss JBPM Token
*/
protected Token getWorkflowToken(GraphSession session, String pathId)
{
// extract process id and token path within process
String[] path = pathId.split("::");
if (path.length != 2)
{
throw new WorkflowException("Invalid workflow path '" + pathId + "'");
}
// retrieve jBPM token for workflow position
ProcessInstance processInstance = session.loadProcessInstance(getJbpmId(path[0]));
Token token = processInstance.findToken(path[1]);
if (token == null)
{
throw new WorkflowException("Workflow path '" + pathId + "' does not exist");
}
return token;
}
//
// Workflow Data Object Creation...
//
/**
* Creates a Workflow Path
*
* @param token JBoss JBPM Token
* @return Workflow Path
*/
protected WorkflowPath createWorkflowPath(Token token)
{
WorkflowPath path = new WorkflowPath();
path.id = createGlobalId(token.getProcessInstance().getId() + "::" + token.getFullName());
path.instance = createWorkflowInstance(token.getProcessInstance());
path.node = createWorkflowNode(token.getNode());
path.active = !token.hasEnded();
return path;
}
/**
* Creates a Workflow Node
*
* @param node JBoss JBPM Node
* @return Workflow Node
*/
@SuppressWarnings("unchecked")
protected WorkflowNode createWorkflowNode(Node node)
{
WorkflowNode workflowNode = new WorkflowNode();
workflowNode.name = node.getName();
if (node instanceof HibernateProxy)
{
Node realNode = (Node)((HibernateProxy)node).getHibernateLazyInitializer().getImplementation();
workflowNode.type = realNode.getClass().getSimpleName();
}
else
{
workflowNode.type = node.getClass().getSimpleName();
}
// TODO: Is there a formal way of determing if task node?
workflowNode.isTaskNode = workflowNode.type.equals("TaskNode");
List transitions = node.getLeavingTransitions();
workflowNode.transitions = new String[transitions.size()];
int i = 0;
for (Transition transition : (List<Transition>)transitions)
{
workflowNode.transitions[i++] = transition.getName();
}
return workflowNode;
}
/**
* Creates a Workflow Instance
*
* @param instance JBoss JBPM Process Instance
* @return Workflow instance
*/
protected WorkflowInstance createWorkflowInstance(ProcessInstance instance)
{
WorkflowInstance workflowInstance = new WorkflowInstance();
workflowInstance.id = createGlobalId(new Long(instance.getId()).toString());
workflowInstance.definition = createWorkflowDefinition(instance.getProcessDefinition());
workflowInstance.active = !instance.hasEnded();
return workflowInstance;
}
/**
* Creates a Workflow Definition
*
* @param definition JBoss Process Definition
* @return Workflow Definition
*/
protected WorkflowDefinition createWorkflowDefinition(ProcessDefinition definition)
{
WorkflowDefinition workflowDef = new WorkflowDefinition();
workflowDef.id = createGlobalId(new Long(definition.getId()).toString());
workflowDef.name = definition.getName();
Task startTask = definition.getTaskMgmtDefinition().getStartTask();
if (startTask != null)
{
workflowDef.startTaskDefinition = createWorkflowTaskDefinition(startTask);
}
return workflowDef;
}
/**
* Creates a Workflow Task
*
* @param task JBoss Task Instance
* @return Workflow Task
*/
protected WorkflowTask createWorkflowTask(TaskInstance task)
{
WorkflowTask workflowTask = new WorkflowTask();
workflowTask.id = createGlobalId(new Long(task.getId()).toString());
workflowTask.name = task.getName();
workflowTask.path = createWorkflowPath(task.getToken());
workflowTask.state = getWorkflowTaskState(task);
workflowTask.definition = createWorkflowTaskDefinition(task.getTask());
// TODO: Properties and Associations
return workflowTask;
}
/**
* Creates a Workflow Task Definition
*
* @param task JBoss JBPM Task
* @return Workflow Task Definition
*/
protected WorkflowTaskDefinition createWorkflowTaskDefinition(Task task)
{
// TODO: Extend jBPM task instance to include dictionary definition qname
WorkflowTaskDefinition taskDef = new WorkflowTaskDefinition();
taskDef.id = task.getName();
QName typeName = QName.createQName(taskDef.id, namespaceService);
taskDef.metadata = dictionaryService.getType(typeName);
return taskDef;
}
/**
* Get the Workflow Task State for the specified JBoss JBPM Task
*
* @param task task
* @return task state
*/
protected WorkflowTaskState getWorkflowTaskState(TaskInstance task)
{
if (task.hasEnded())
{
return WorkflowTaskState.COMPLETED;
}
else
{
return WorkflowTaskState.IN_PROGRESS;
}
}
}

View File

@@ -0,0 +1,215 @@
/*
* 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.jbpm;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.workflow.BPMEngineRegistry;
import org.alfresco.repo.workflow.TaskComponent;
import org.alfresco.repo.workflow.WorkflowComponent;
import org.alfresco.repo.workflow.WorkflowDefinitionComponent;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.BaseSpringTest;
/**
* JBPM Engine Tests
*
* @author davidc
*/
public class JBPMEngineTest extends BaseSpringTest
{
WorkflowDefinitionComponent workflowDefinitionComponent;
WorkflowComponent workflowComponent;
TaskComponent taskComponent;
//@Override
protected void onSetUpInTransaction() throws Exception
{
BPMEngineRegistry registry = (BPMEngineRegistry)applicationContext.getBean("bpm_engineRegistry");
workflowDefinitionComponent = registry.getWorkflowDefinitionComponent("jbpm");
workflowComponent = registry.getWorkflowComponent("jbpm");
taskComponent = registry.getTaskComponent("jbpm");
}
public void testGetWorkflowDefinitions()
{
List<WorkflowDefinition> workflowDefs = workflowDefinitionComponent.getDefinitions();
assertNotNull(workflowDefs);
assertTrue(workflowDefs.size() > 0);
}
public void testStartWorkflow()
{
try
{
@SuppressWarnings("unused") WorkflowPath path = workflowComponent.startWorkflow("norfolknchance", null);
fail("Failed to catch invalid definition id");
}
catch(WorkflowException e)
{
}
// TODO: Determine why process definition is loaded, even though it doesn't exist
// try
// {
// @SuppressWarnings("unused") WorkflowPosition pos = workflowComponent.startProcess("1000", null);
// fail("Failed to catch workflow definition id that does not exist");
// }
// catch(WorkflowException e)
// {
// }
WorkflowDefinition workflowDef = getTestDefinition();
WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, null);
assertNotNull(path);
assertTrue(path.id.endsWith("::/"));
assertNotNull(path.node);
assertNotNull(path.instance);
assertEquals(workflowDef.id, path.instance.definition.id);
}
public void testGetWorkflowInstances()
{
WorkflowDefinition workflowDef = getTestDefinition();
workflowComponent.startWorkflow(workflowDef.id, null);
workflowComponent.startWorkflow(workflowDef.id, null);
List<WorkflowInstance> instances = workflowComponent.getActiveWorkflows(workflowDef.id);
assertNotNull(instances);
assertEquals(2, instances.size());
for (WorkflowInstance instance : instances)
{
assertEquals(workflowDef.id, instance.definition.id);
}
}
public void testGetPositions()
{
WorkflowDefinition workflowDef = getTestDefinition();
workflowComponent.startWorkflow(workflowDef.id, null);
List<WorkflowInstance> instances = workflowComponent.getActiveWorkflows(workflowDef.id);
assertNotNull(instances);
assertEquals(1, instances.size());
List<WorkflowPath> paths = workflowComponent.getWorkflowPaths(instances.get(0).id);
assertNotNull(paths);
assertEquals(1, paths.size());
assertEquals(instances.get(0).id, paths.get(0).instance.id);
assertTrue(paths.get(0).id.endsWith("::/"));
}
public void testCancelWorkflowInstance()
{
WorkflowDefinition workflowDef = getTestDefinition();
workflowComponent.startWorkflow(workflowDef.id, null);
List<WorkflowInstance> instances1 = workflowComponent.getActiveWorkflows(workflowDef.id);
assertNotNull(instances1);
assertEquals(1, instances1.size());
WorkflowInstance cancelledInstance = workflowComponent.cancelWorkflow(instances1.get(0).id);
assertNotNull(cancelledInstance);
assertFalse(cancelledInstance.active);
List<WorkflowInstance> instances2 = workflowComponent.getActiveWorkflows(workflowDef.id);
assertNotNull(instances2);
assertEquals(0, instances2.size());
}
public void testSignal()
{
WorkflowDefinition workflowDef = getTestDefinition();
Map<QName, Serializable> parameters = new HashMap<QName, Serializable>();
parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin");
WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters);
assertNotNull(path);
WorkflowPath updatedPath = workflowComponent.signal(path.id, path.node.transitions[0]);
assertNotNull(updatedPath);
}
public void testGetAssignedTasks()
{
WorkflowDefinition workflowDef = getTestDefinition();
Map<QName, Serializable> parameters = new HashMap<QName, Serializable>();
parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin");
WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters);
assertNotNull(path);
assertNotNull(path);
WorkflowPath updatedPath = workflowComponent.signal(path.id, path.node.transitions[0]);
assertNotNull(updatedPath);
List<WorkflowTask> completedTasks = taskComponent.getAssignedTasks("admin", WorkflowTaskState.COMPLETED);
assertNotNull(completedTasks);
assertEquals(0, completedTasks.size());
List<WorkflowTask> assignedTasks = taskComponent.getAssignedTasks("admin", WorkflowTaskState.IN_PROGRESS);
assertNotNull(assignedTasks);
assertEquals(1, assignedTasks.size());
assertEquals("Review", assignedTasks.get(0).name);
}
public void testEndTask()
{
WorkflowDefinition workflowDef = getTestDefinition();
Map<QName, Serializable> parameters = new HashMap<QName, Serializable>();
parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin");
WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters);
assertNotNull(path);
assertNotNull(path);
List<WorkflowTask> tasks1 = workflowComponent.getTasksForWorkflowPath(path.id);
assertNotNull(tasks1);
assertEquals(1, tasks1.size());
assertEquals(WorkflowTaskState.IN_PROGRESS, tasks1.get(0).state);
WorkflowTask updatedTask = taskComponent.endTask(tasks1.get(0).id, null);
assertNotNull(updatedTask);
assertEquals(WorkflowTaskState.COMPLETED, updatedTask.state);
}
/**
* Locate the Test Workflow Definition
*
* @return workflow definition
*/
private WorkflowDefinition getTestDefinition()
{
List<WorkflowDefinition> workflowDefs = workflowDefinitionComponent.getDefinitions();
for (WorkflowDefinition workflowDef : workflowDefs)
{
if (workflowDef.name.equals("Review and Approve"))
{
return workflowDef;
}
}
fail("Test Workflow Definition not found");
return null;
}
}

View File

@@ -38,6 +38,7 @@ import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.cmr.view.ExporterService;
import org.alfresco.service.cmr.view.ImporterService;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
@@ -81,6 +82,7 @@ public interface ServiceRegistry
static final QName TEMPLATE_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "TemplateService");
static final QName FILE_FOLDER_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "FileFolderService");
static final QName SCRIPT_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "ScriptService");
static final QName WORKFLOW_SERVICE = QName.createQName(NamespaceService.ALFRESCO_URI, "WorkflowService");
/**
* Get the list of services provided by the Repository
@@ -254,4 +256,10 @@ public interface ServiceRegistry
*/
@NotAuditable
ScriptService getScriptService();
/**
* @return the workflow service (or null if one is not provided)
*/
@NotAuditable
WorkflowService getWorkflowService();
}

View File

@@ -0,0 +1,44 @@
/*
* 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.service.cmr.workflow;
/**
* Workflow Definition Data Object
*
* @author davidc
*/
public class WorkflowDefinition
{
/** Workflow Definition unique id */
public String id;
/** Workflow Definition name */
public String name;
/** Task Definition for Workflow Start Task (Optional) */
public WorkflowTaskDefinition startTaskDefinition;
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
return "WorkflowDefinition[id=" + id + ",name=" + name + ",startTask=" + startTaskDefinition.toString() + "]";
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.service.cmr.workflow;
import org.alfresco.error.AlfrescoRuntimeException;
/**
* Base Exception of Workflow Exceptions.
*
* @author David Caruana
*/
public class WorkflowException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = -7338963365877285084L;
public WorkflowException(String msgId)
{
super(msgId);
}
public WorkflowException(String msgId, Throwable cause)
{
super(msgId, cause);
}
public WorkflowException(String msgId, Object ... args)
{
super(msgId, args);
}
public WorkflowException(String msgId, Throwable cause, Object ... args)
{
super(msgId, args, cause);
}
}

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.service.cmr.workflow;
/**
* Workflow Instance Data Object
*
* Represents an "in-flight" workflow.
*
* @author davidc
*/
public class WorkflowInstance
{
/** Workflow Instance unique id */
public String id;
/** Is this Workflow instance still "in-flight" or has it completed? */
public boolean active;
/** Workflow Definition */
public WorkflowDefinition definition;
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
return "WorkflowInstance[id=" + id + ",active=" + active + ",def=" + definition.toString() + "]";
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.service.cmr.workflow;
/**
* Workflow Node Data Object
*
* Represents a Node within the Workflow Definition.
*
* @author davidc
*/
public class WorkflowNode
{
/** Name of the Workflow Node */
public String name;
/** Type of the Workflow Node (typically this is BPM engine specific - informational only */
public String type;
/** Does this Workflow Node represent human interaction? */
public boolean isTaskNode;
/** The transitions leaving this node (or null, if none) */
public String[] transitions;
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
String transitionsArray = "{";
for (int i = 0; i < transitions.length; i++)
{
transitionsArray += ((i == 0) ? "" : ",") + "'" + transitions[i] + "'";
}
transitionsArray += "}";
return "WorkflowNode[name=" + name + ",type=" + type + ",transitions=" + transitionsArray + "]";
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.service.cmr.workflow;
/**
* Workflow Path Data Object
*
* Represents a path within an "in-flight" workflow instance.
*
* Simple workflows consists of a single "root" path. Multiple paths occur when a workflow
* instance branches, therefore more than one concurrent path is taken.
*
* @author davidc
*/
public class WorkflowPath
{
/** Unique id of Workflow Path */
public String id;
/** Workflow Instance this path is part of */
public WorkflowInstance instance;
/** The Workflow Node the path is at */
public WorkflowNode node;
/** Is the path still active? */
public boolean active;
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
return "WorkflowPath[id=" + id + ",instance=" + instance.toString() + ",active=" + active + ",node=" + node.toString()+ "]";
}
}

View File

@@ -0,0 +1,194 @@
/*
* 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.service.cmr.workflow;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
/**
* Workflow Service.
*
* Client facing API for interacting with Alfresco Workflows and Tasks.
*
* @author davidc
*/
public interface WorkflowService
{
//
// Workflow Definition Management
//
/**
* Deploy a Workflow Definition to the Alfresco Repository
*
* Note: The specified content object must be of type bpm:workflowdefinition.
* This type describes for which BPM engine the definition is appropriate.
*
* @param workflowDefinition the content object containing the definition
* @return workflow definition
*/
public WorkflowDefinition deployDefinition(NodeRef workflowDefinition);
/**
* Undeploy an exisiting Workflow Definition
*
* TODO: Determine behaviour when "in-flight" workflow instances exist
*
* @param workflowDefinitionId the id of the definition to undeploy
*/
public void undeployDefinition(String workflowDefinitionId);
/**
* Gets all deployed Workflow Definitions
*
* @return the deployed workflow definitions
*/
public List<WorkflowDefinition> getDefinitions();
/**
* Gets a Workflow Definition by unique Id
*
* @param workflowDefinitionId the workflow definition id
* @return the deployed workflow definition
*/
public WorkflowDefinition getDefinitionById(String workflowDefinitionId);
//
// Workflow Instance Management
//
/**
* Start a Workflow Instance
*
* @param workflowDefinitionId the workflow definition id
* @param parameters the initial set of parameters used to populate the "Start Task" properties
* @return the initial workflow path
*/
public WorkflowPath startWorkflow(String workflowDefinitionId, Map<QName, Serializable> parameters);
/**
* Start a Workflow Instance from an existing "Start Task" template node held in the
* Repository. The node must be of the Type as described in the Workflow Definition.
*
* @param templateDefinition the node representing the Start Task properties
* @return the initial workflow path
*/
public WorkflowPath startWorkflowFromTemplate(NodeRef templateDefinition);
/**
* Gets all "in-flight" workflow instances of the specified Workflow Definition
*
* @param workflowDefinitionId the workflow definition id
* @return the list of "in-fligth" workflow instances
*/
public List<WorkflowInstance> getActiveWorkflows(String workflowDefinitionId);
/**
* Gets all Paths for the specified Workflow instance
*
* @param workflowId workflow instance id
* @return the list of workflow paths
*/
public List<WorkflowPath> getWorkflowPaths(String workflowId);
/**
* Cancel an "in-fligth" Workflow instance
*
* @param workflowId the workflow instance to cancel
* @return an updated representation of the workflow instance
*/
public WorkflowInstance cancelWorkflow(String workflowId);
/**
* Signal the transition from one Workflow Node to another
*
* @param pathId the workflow path to signal on
* @param transition the transition to follow (or null, for the default transition)
* @return the updated workflow path
*/
public WorkflowPath signal(String pathId, String transition);
/**
* Gets all Tasks associated with the specified path
*
* @param pathId the path id
* @return the list of associated tasks
*/
public List<WorkflowTask> getTasksForWorkflowPath(String pathId);
//
// Task Management
//
/**
* Gets a Task by unique Id
*
* @param taskId the task id
* @return the task
*/
public WorkflowTask getTaskById(String taskId);
/**
* Gets all tasks assigned to the specified authority
*
* @param authority the authority
* @param state filter by specified workflow task state
* @return the list of assigned tasks
*/
public List<WorkflowTask> getAssignedTasks(String authority, WorkflowTaskState state);
/**
* Gets the pooled tasks available to the specified authority
*
* @param authority the authority
* @return the list of pooled tasks
*/
public List<WorkflowTask> getPooledTasks(String authority);
/**
* Update the Properties and Associations of a Task
*
* @param taskId the task id to update
* @param properties the map of properties to set on the task (or null, if none to set)
* @param add the map of items to associate with the task (or null, if none to add)
* @param remove the map of items to dis-associate with the task (or null, if none to remove)
* @return the update task
*/
public WorkflowTask updateTask(String taskId, Map<QName, Serializable> properties, Map<QName, List<NodeRef>> add, Map<QName, List<NodeRef>> remove);
/**
* End the Task (i.e. complete the task)
*
* @param taskId the task id to end
* @param transition the task transition to take on completion (or null, for the default transition)
* @return the updated task
*/
public WorkflowTask endTask(String taskId, String transition);
// todo: workflow package apis
// createPackage
}

View File

@@ -0,0 +1,66 @@
/*
* 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.service.cmr.workflow;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
/**
* Workflow Task Data Object
*
* Represents a human-oriented task within an "in-fligth" workflow instance
*
* @author davidc
*/
public class WorkflowTask
{
/** Unique id of Task */
public String id;
/** Name of Task */
public String name;
/** Task State */
public WorkflowTaskState state;
/** Workflow path this Task is associated with */
public WorkflowPath path;
/** Task Definition */
public WorkflowTaskDefinition definition;
/** Task Properties as described by Task Definition */
public Map<QName, Serializable> properties;
/** Task Associations as described by Task Definition */
public Map<QName, List<NodeRef>> associations;
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
return "WorkflowTask[id=" + id + ",name=" + name + ",state=" + state + ",def=" + definition + ",path=" + path.toString() + "]";
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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.service.cmr.workflow;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
/**
* Workflow Task Definition Data Object.
*
* Represents meta-data for a Workflow Task. The meta-data is described in terms
* of the Alfresco Data Dictionary.
*
* @author davidc
*/
public class WorkflowTaskDefinition
{
/** Unique id of Workflow Task Definition */
public String id;
// TODO: Convert to TaskDefinition (derived from TypeDefinition)
/** Task Metadata */
public TypeDefinition metadata;
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
return "WorkflowTaskDefinition[id=" + id + ",metadata=" + metadata + "]";
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.service.cmr.workflow;
/**
* Workflow Task State
*
* Represents the high-level state of Workflow Task (in relation to "in-flight"
* workflow instance).
*
* A user-defined task state may be represented as Task Property (and described
* by the Alfresco Data Dictionary).
*
* @author davidc
*/
public enum WorkflowTaskState
{
IN_PROGRESS,
COMPLETED;
}