From 25123a45040ca9bb9d3296d2c72bda85a76c554f Mon Sep 17 00:00:00 2001 From: David Caruana Date: Fri, 28 Jul 2006 22:58:02 +0000 Subject: [PATCH] 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 --- config/alfresco/public-services-context.xml | 36 + config/alfresco/workflow-context.xml | 33 +- .../service/ServiceDescriptorRegistry.java | 9 + .../org/alfresco/repo/workflow/BPMEngine.java | 100 +++ .../repo/workflow/BPMEngineRegistry.java | 234 ++++++ .../alfresco/repo/workflow/TaskComponent.java | 83 ++ .../repo/workflow/WorkflowComponent.java | 89 ++ .../workflow/WorkflowDefinitionComponent.java | 66 ++ .../repo/workflow/WorkflowServiceImpl.java | 279 +++++++ .../workflow/WorkflowServiceImplTest.java | 63 ++ .../repo/workflow/jbpm/JBPMEngine.java | 757 ++++++++++++++++++ .../repo/workflow/jbpm/JBPMEngineTest.java | 215 +++++ .../org/alfresco/service/ServiceRegistry.java | 8 + .../cmr/workflow/WorkflowDefinition.java | 44 + .../cmr/workflow/WorkflowException.java | 49 ++ .../cmr/workflow/WorkflowInstance.java | 45 ++ .../service/cmr/workflow/WorkflowNode.java | 55 ++ .../service/cmr/workflow/WorkflowPath.java | 52 ++ .../service/cmr/workflow/WorkflowService.java | 194 +++++ .../service/cmr/workflow/WorkflowTask.java | 66 ++ .../cmr/workflow/WorkflowTaskDefinition.java | 47 ++ .../cmr/workflow/WorkflowTaskState.java | 35 + 22 files changed, 2556 insertions(+), 3 deletions(-) create mode 100644 source/java/org/alfresco/repo/workflow/BPMEngine.java create mode 100644 source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java create mode 100644 source/java/org/alfresco/repo/workflow/TaskComponent.java create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowComponent.java create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowDefinitionComponent.java create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java create mode 100644 source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowException.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowService.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java create mode 100644 source/java/org/alfresco/service/cmr/workflow/WorkflowTaskState.java diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index e536595323..2fe31c568d 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -934,5 +934,41 @@ + + + + + + + org.alfresco.service.cmr.workflow.WorkflowService + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + org.alfresco.service.cmr.workflow.WorkflowService + + + Workflow Service + + diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index 1c49209e27..010c82854e 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -3,7 +3,29 @@ - + + + + + + + + + + + + + + + + + + + + + + + @@ -12,14 +34,19 @@ - - + + + + + + + diff --git a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java index d7e846d429..eb018bc77a 100644 --- a/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java +++ b/source/java/org/alfresco/repo/service/ServiceDescriptorRegistry.java @@ -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); + } } diff --git a/source/java/org/alfresco/repo/workflow/BPMEngine.java b/source/java/org/alfresco/repo/workflow/BPMEngine.java new file mode 100644 index 0000000000..7083475f0c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/BPMEngine.java @@ -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); + } +} diff --git a/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java b/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java new file mode 100644 index 0000000000..c2024b0a6d --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/BPMEngineRegistry.java @@ -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 workflowDefinitionComponents; + private Map workflowComponents; + private Map taskComponents; + + + /** + * Construct + */ + public BPMEngineRegistry() + { + workflowDefinitionComponents = new HashMap(); + workflowComponents = new HashMap(); + taskComponents = new HashMap(); + } + + /** + * 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]; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/TaskComponent.java b/source/java/org/alfresco/repo/workflow/TaskComponent.java new file mode 100644 index 0000000000..8fabe54b78 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/TaskComponent.java @@ -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 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 getPooledTasks(List 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 properties, Map> add, Map> 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); + +} + diff --git a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java new file mode 100644 index 0000000000..a0f742e866 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java @@ -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 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 getActiveWorkflows(String workflowDefinitionId); + + /** + * Gets all Paths for the specified Workflow instance + * + * @param workflowId workflow instance id + * @return the list of workflow paths + */ + public List 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 getTasksForWorkflowPath(String pathId); + +} + diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDefinitionComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowDefinitionComponent.java new file mode 100644 index 0000000000..5fd9ef849b --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowDefinitionComponent.java @@ -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 getDefinitions(); + + /** + * Gets a Workflow Definition by unique Id + * + * @param workflowDefinitionId the workflow definition id + * @return the deployed workflow definition + */ + public WorkflowDefinition getDefinitionById(String workflowDefinitionId); + +} + diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java new file mode 100644 index 0000000000..c7b10a776c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -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 getDefinitions() + { + List definitions = new ArrayList(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 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 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 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 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 getAssignedTasks(String authority, WorkflowTaskState state) + { + List tasks = new ArrayList(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 getPooledTasks(String authority) + { + // TODO: Expand authorities to include associated groups (and parent groups) + List authorities = new ArrayList(); + authorities.add(authority); + + List tasks = new ArrayList(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 properties, Map> add, Map> 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; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java new file mode 100644 index 0000000000..c41b546332 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImplTest.java @@ -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 workflowDefs = workflowService.getDefinitions(); + assertNotNull(workflowDefs); + assertTrue(workflowDefs.size() > 0); + } + + public void testStartWorkflow() + { + List 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); + } +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java new file mode 100644 index 0000000000..8f58734b9c --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -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 getDefinitions() + { + try + { + return (List)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processDefs = (List)graphSession.findLatestProcessDefinitions(); + List workflowDefs = new ArrayList(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 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 getActiveWorkflows(final String workflowDefinitionId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processInstances = graphSession.findProcessInstances(getJbpmId(workflowDefinitionId)); + List workflowInstances = new ArrayList(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 getWorkflowPaths(final String workflowId) + { + try + { + return (List) 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 tokens = processInstance.findAllTokens(); + List paths = new ArrayList(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 getTasksForWorkflowPath(final String pathId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve tasks at specified workflow path + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findTaskInstancesByToken(token.getId()); + List workflowTasks = new ArrayList(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 getAssignedTasks(final String authority, final WorkflowTaskState state) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve tasks assigned to authority + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findTaskInstances(authority); + List workflowTasks = new ArrayList(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 getPooledTasks(final List authorities) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve pooled tasks for specified authorities + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findPooledTaskInstances(authorities); + List workflowTasks = new ArrayList(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 properties, Map> add, Map> 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 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 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)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; + } + } + +} diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java new file mode 100644 index 0000000000..2145f4b3ea --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java @@ -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 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 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 instances = workflowComponent.getActiveWorkflows(workflowDef.id); + assertNotNull(instances); + assertEquals(1, instances.size()); + List 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 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 instances2 = workflowComponent.getActiveWorkflows(workflowDef.id); + assertNotNull(instances2); + assertEquals(0, instances2.size()); + } + + + public void testSignal() + { + WorkflowDefinition workflowDef = getTestDefinition(); + Map parameters = new HashMap(); + 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 parameters = new HashMap(); + 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 completedTasks = taskComponent.getAssignedTasks("admin", WorkflowTaskState.COMPLETED); + assertNotNull(completedTasks); + assertEquals(0, completedTasks.size()); + List 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 parameters = new HashMap(); + parameters.put(QName.createQName(NamespaceService.DEFAULT_URI, "reviewer"), "admin"); + WorkflowPath path = workflowComponent.startWorkflow(workflowDef.id, parameters); + assertNotNull(path); + assertNotNull(path); + List 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 workflowDefs = workflowDefinitionComponent.getDefinitions(); + for (WorkflowDefinition workflowDef : workflowDefs) + { + if (workflowDef.name.equals("Review and Approve")) + { + return workflowDef; + } + } + fail("Test Workflow Definition not found"); + return null; + } + +} diff --git a/source/java/org/alfresco/service/ServiceRegistry.java b/source/java/org/alfresco/service/ServiceRegistry.java index 3d26bcbf29..3fc80541da 100644 --- a/source/java/org/alfresco/service/ServiceRegistry.java +++ b/source/java/org/alfresco/service/ServiceRegistry.java @@ -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(); } diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java new file mode 100644 index 0000000000..d427ecf63f --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java @@ -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() + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowException.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowException.java new file mode 100644 index 0000000000..e776ed6e18 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowException.java @@ -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); + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java new file mode 100644 index 0000000000..07c0dac232 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowInstance.java @@ -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() + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java new file mode 100644 index 0000000000..ad0fdf040c --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowNode.java @@ -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 + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java new file mode 100644 index 0000000000..c187dfd3c9 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowPath.java @@ -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()+ "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java new file mode 100644 index 0000000000..fe573799b0 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -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 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 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 getActiveWorkflows(String workflowDefinitionId); + + /** + * Gets all Paths for the specified Workflow instance + * + * @param workflowId workflow instance id + * @return the list of workflow paths + */ + public List 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 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 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 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 properties, Map> add, Map> 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 + +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java new file mode 100644 index 0000000000..07fe5f2411 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTask.java @@ -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 properties; + + /** Task Associations as described by Task Definition */ + public Map> associations; + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowTask[id=" + id + ",name=" + name + ",state=" + state + ",def=" + definition + ",path=" + path.toString() + "]"; + } + +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java new file mode 100644 index 0000000000..6ab275b576 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskDefinition.java @@ -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 + "]"; + } +} diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskState.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskState.java new file mode 100644 index 0000000000..89c6d259ec --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTaskState.java @@ -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; +}