diff --git a/config/alfresco/messages/workflow-interpreter-help.txt b/config/alfresco/messages/workflow-interpreter-help.txt index 35d9fbf282..001ee36cbb 100644 --- a/config/alfresco/messages/workflow-interpreter-help.txt +++ b/config/alfresco/messages/workflow-interpreter-help.txt @@ -43,9 +43,11 @@ ok> redeploy Redeploy the last workflow definition. -ok> show definitions +ok> show definitions [all] - List all deployed workflow definitions. + List latest deployed workflow definitions. Or, display all workflows + definitions (including previous versions) with the additional keyword + 'all'. ok> use definition [] @@ -60,7 +62,14 @@ ok> undeploy definition workflows associated with the definition. If multiple versions of the definition exist, you will have to undeploy - each in turn to remove the definition completely. + each in turn to remove the definition completely or issue the 'undeploy + definition name' command. + +ok> undeploy definition name + + Undeploy all versions of a workflow definition. As with 'undeploy + definition', all "in-flight" workflows associated with each version + are terminated. ## ## Variable Commands @@ -140,18 +149,27 @@ ok> start []]* ok> show workflows [all] Display the list of active workflows for the currently selected workflow - definition. Or, display the list of all workflows when used with additional - keyword 'all'. + definition. Or, display the list of all workflows when used with the + additional keyword 'all'. ok> use workflow Use the specified . +ok> desc workflow + + Describe the specified . + ok> show paths [] Display the workflow paths for the specified . If is omitted, the paths for the currently started workflow are shown. +ok> desc path + + Describe the specified . Includes the list of properties associated + with the path. + ok> show transitions [] Display all available transitions for the specified . If @@ -163,9 +181,9 @@ ok> signal [] Signal transition on specified . If is omitted, the default transition is taken. -ok> desc workflow +ok> event - Describe the specified . + Fire an event of custom 'eventtype' against the specified path. ok> end workflow @@ -179,6 +197,16 @@ ok> delete all workflows Force deletion of all "in-flight" workflows. Use with care! +## +## Timer Commands +## + +ok> show timers [all] + + Display the list of active timers for the currently selected workflow + definition. Or, display the list of all timers when used with the + additional keyword 'all'. + ## ## Task Commands ## @@ -222,6 +250,44 @@ ok> end task [] End the task identified by . If is omitted, the default transition is taken. + +ok> query task [predicate]* + + Query for tasks. If no predicates are provided, all "in-progress" tasks + are returned (across all "active" workflows). + + Predicates are: + + taskId= + taskName= e.g. taskName=wf:reviewTask + taskState=IN_PROGRESS|COMPLETED + taskActor= e.g. taskActor=admin + task.= e.g. task.bpm:outcome=approve + processId= + processName= e.g. processName=wf:review + processActive=true|false e.g. processActive=true + process.= e.g. process.initiator=admin + orderBy=* e.g. orderBy=TaskDue_Desc,TaskActor_Asc + + Where is one of: + + TaskId_Asc, + TaskId_Desc, + TaskCreated_Asc, + TaskCreated_Desc, + TaskDue_Asc, + TaskDue_Desc, + TaskName_Asc, + TaskName_Desc, + TaskActor_Asc, + TaskActor_Desc, + TaskState_Asc, + TaskState_Desc + + e.g. query all in-progress pending submissions for web project X, ordered by pending due date, submitter + + query task taskName=wcmwf:submitpending taskState=IN_PROGRESS \ + process.wcwmf:webproject=workspace://SpacesStore/projectx orderBy=TaskDue_Desc,TaskActor_Asc ## ## end diff --git a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java index 43f8fb8358..706ac4ab6f 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java @@ -35,6 +35,7 @@ import org.alfresco.service.cmr.workflow.WorkflowDeployment; 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.WorkflowTimer; import org.alfresco.service.namespace.QName; @@ -88,6 +89,14 @@ public interface WorkflowComponent */ public List getDefinitions(); + /** + * Gets all deployed Workflow Definitions (with all previous versions) + * + * @return the deployed (and previous) workflow definitions + */ + @Auditable + public List getAllDefinitions(); + /** * Gets a Workflow Definition by unique Id * @@ -104,6 +113,15 @@ public interface WorkflowComponent */ public WorkflowDefinition getDefinitionByName(String workflowName); + /** + * Gets all (including previous) Workflow Definitions for the given unique name + * + * @param workflowName workflow name e.g. jbpm://review + * @return the deployed workflow definition (or null if not found) + */ + @Auditable(parameters = {"workflowName"}) + public List getAllDefinitionsByName(String workflowName); + /** * Gets a graphical view of the Workflow Definition * @@ -152,6 +170,14 @@ public interface WorkflowComponent */ public List getWorkflowPaths(String workflowId); + /** + * Gets the properties associated with the specified path (and parent paths) + * + * @param pathId workflow path id + * @return map of path properties + */ + public Map getPathProperties(String pathId); + /** * Cancel an "in-fligth" Workflow instance * @@ -178,6 +204,15 @@ public interface WorkflowComponent */ public WorkflowPath signal(String pathId, String transitionId); + /** + * Fire custom event against specified path + * + * @param pathId the workflow path to fire event on + * @param event name of event + * @return workflow path (it may have been updated as a result of firing the event + */ + public WorkflowPath fireEvent(String pathId, String event); + /** * Gets all Tasks associated with the specified path * @@ -185,6 +220,13 @@ public interface WorkflowComponent * @return the list of associated tasks */ public List getTasksForWorkflowPath(String pathId); + + /** + * Gets all active timers for the specified workflow + * + * @return the list of active timers + */ + public List getTimers(String workflowId); } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java index 73fccafa96..0aee27bc2a 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java @@ -37,17 +37,17 @@ import java.util.List; import java.util.Map; import org.alfresco.i18n.I18NUtil; -import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.model.ContentModel; +import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avmsync.AVMDifference; import org.alfresco.service.cmr.avmsync.AVMSyncService; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.repo.security.authority.AuthorityDAO; -import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ContentWriter; @@ -61,7 +61,9 @@ 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.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; import org.alfresco.service.cmr.workflow.WorkflowTransition; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -351,7 +353,22 @@ public class WorkflowInterpreter else if (command[1].equals("definitions")) { - List defs = workflowService.getDefinitions(); + List defs = null; + if (command.length == 3) + { + if (command[2].equals("all")) + { + defs = workflowService.getAllDefinitions(); + } + else + { + return "Syntax Error.\n"; + } + } + else + { + defs = workflowService.getDefinitions(); + } for (WorkflowDefinition def : defs) { out.println("id: " + def.id + " , name: " + def.name + " , title: " + def.title + " , version: " + def.version); @@ -379,12 +396,12 @@ public class WorkflowInterpreter if (id.equals("all")) { - for (WorkflowDefinition def : workflowService.getDefinitions()) + for (WorkflowDefinition def : workflowService.getAllDefinitions()) { List workflows = workflowService.getActiveWorkflows(def.id); for (WorkflowInstance workflow : workflows) { - out.println("id: " + workflow.id + " , desc: " + workflow.description + " , start date: " + workflow.startDate + " , def: " + workflow.definition.title); + out.println("id: " + workflow.id + " , desc: " + workflow.description + " , start date: " + workflow.startDate + " , def: " + workflow.definition.name + " v" + workflow.definition.version); } } } @@ -393,7 +410,7 @@ public class WorkflowInterpreter List workflows = workflowService.getActiveWorkflows(id); for (WorkflowInstance workflow : workflows) { - out.println("id: " + workflow.id + " , desc: " + workflow.description + " , start date: " + workflow.startDate + " , def: " + workflow.definition.title); + out.println("id: " + workflow.id + " , desc: " + workflow.description + " , start date: " + workflow.startDate + " , def: " + workflow.definition.name); } } } @@ -453,6 +470,54 @@ public class WorkflowInterpreter } } + else if (command[1].equals("timers")) + { + String id = (currentWorkflowDef != null) ? currentWorkflowDef.id : null; + if (id == null && command.length == 2) + { + return "workflow definition not in use. Enter command 'show timers all' or 'use '.\n"; + } + if (command.length == 3) + { + if (command[2].equals("all")) + { + id = "all"; + } + else + { + return "Syntax Error.\n"; + } + } + + if (id.equals("all")) + { + for (WorkflowDefinition def : workflowService.getAllDefinitions()) + { + List workflows = workflowService.getActiveWorkflows(def.id); + for (WorkflowInstance workflow : workflows) + { + List timers = workflowService.getTimers(workflow.id); + for (WorkflowTimer timer : timers) + { + out.println("id: " + timer.id + " , name: " + timer.name + " , due date: " + timer.dueDate + " , path: " + timer.path.id + " , node: " + timer.path.node.name + " , process: " + timer.path.instance.id + " , task: " + timer.task.name); + } + } + } + } + else + { + List workflows = workflowService.getActiveWorkflows(id); + for (WorkflowInstance workflow : workflows) + { + List timers = workflowService.getTimers(workflow.id); + for (WorkflowTimer timer : timers) + { + out.println("id: " + timer.id + " , name: " + timer.name + " , due date: " + timer.dueDate + " , path: " + timer.path.id + " , node: " + timer.path.node.name + " , process: " + timer.path.instance.id + " , task: " + timer.task.name); + } + } + } + } + else if (command[1].equals("my")) { if (command.length != 3) @@ -550,6 +615,144 @@ public class WorkflowInterpreter out.println("context: " + workflow.context); out.println("package: " + workflow.workflowPackage); } + + else if (command[1].equals("path")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + Map properties = workflowService.getPathProperties(command[2]); + out.println("path: " + command[1]); + out.println("properties: " + properties.size()); + for (Map.Entry prop : properties.entrySet()) + { + out.println(" " + prop.getKey() + " = " + prop.getValue()); + } + } + + else + { + return "Syntax Error.\n"; + } + } + + else if (command[0].equals("query")) + { + if (command.length < 2) + { + return "Syntax Error.\n"; + } + + if (command[1].equals("task")) + { + // build query + WorkflowTaskQuery query = new WorkflowTaskQuery(); + Map taskProps = new HashMap(); + Map procProps = new HashMap(); + + for (int i = 2; i < command.length; i++) + { + String[] predicate = command[i].split("="); + if (predicate.length == 1) + { + return "Syntax Error.\n"; + } + String[] predicateName = predicate[0].split("\\."); + if (predicateName.length == 1) + { + if (predicate[0].equals("taskId")) + { + query.setTaskId(predicate[1]); + } + else if (predicate[0].equals("taskState")) + { + WorkflowTaskState state = WorkflowTaskState.valueOf(predicate[1]); + if (state == null) + { + return "Syntax Error. Unknown task state\n"; + } + query.setTaskState(state); + } + else if (predicate[0].equals("taskName")) + { + query.setTaskName(QName.createQName(predicate[1], namespaceService)); + } + else if (predicate[0].equals("taskActor")) + { + query.setActorId(predicate[1]); + } + else if (predicate[0].equals("processId")) + { + query.setProcessId(predicate[1]); + } + else if (predicate[0].equals("processName")) + { + query.setProcessName(QName.createQName(predicate[1], namespaceService)); + } + else if (predicate[0].equals("processActive")) + { + Boolean active = Boolean.valueOf(predicate[1]); + query.setActive(active); + } + else if (predicate[0].equals("orderBy")) + { + String[] orderBy = predicate[1].split(","); + WorkflowTaskQuery.OrderBy[] queryOrderBy = new WorkflowTaskQuery.OrderBy[orderBy.length]; + for (int iOrderBy = 0; iOrderBy < orderBy.length; iOrderBy++) + { + queryOrderBy[iOrderBy] = WorkflowTaskQuery.OrderBy.valueOf(orderBy[iOrderBy]); + if (queryOrderBy[iOrderBy] == null) + { + return "Syntax Error. Unknown orderBy.\n"; + } + } + query.setOrderBy(queryOrderBy); + } + else + { + return "Syntax Error. Unknown query predicate.\n"; + } + } + else if (predicateName.length == 2) + { + if (predicateName[0].equals("task")) + { + taskProps.put(QName.createQName(predicateName[1], namespaceService), predicate[1]); + } + else if (predicateName[0].equals("process")) + { + procProps.put(QName.createQName(predicateName[1], namespaceService), predicate[1]); + } + else + { + return "Syntax Error. Unknown query predicate.\n"; + } + } + else + { + return "Syntax Error.\n"; + } + } + + if (taskProps.size() > 0) + { + query.setTaskCustomProps(taskProps); + } + if (procProps.size() > 0) + { + query.setProcessCustomProps(procProps); + } + + // execute query + List tasks = workflowService.queryTasks(query); + out.println("found " + tasks.size() + " tasks."); + for (WorkflowTask task : tasks) + { + out.println("task id: " + task.id + " , name: " + task.name + " , properties: " + task.properties.size() + ", process id: " + task.path.instance); + } + } + else { return "Syntax Error.\n"; @@ -591,14 +794,38 @@ public class WorkflowInterpreter } if (command[1].equals("definition")) { - if (command.length != 3) + if (command.length == 3) + { + workflowService.undeployDefinition(command[2]); + currentWorkflowDef = null; + currentPath = null; + out.print(executeCommand("show definitions")); + } + else if (command.length == 4) + { + if (command[2].equals("name")) + { + out.print("undeploying..."); + List defs = workflowService.getAllDefinitionsByName(command[3]); + for (WorkflowDefinition def: defs) + { + workflowService.undeployDefinition(def.id); + out.print(" v" + def.version); + } + out.println(""); + currentWorkflowDef = null; + currentPath = null; + out.print(executeCommand("show definitions all")); + } + else + { + return "Syntax Error.\n"; + } + } + else { return "Syntax Error.\n"; } - workflowService.undeployDefinition(command[2]); - currentWorkflowDef = null; - currentPath = null; - out.print(executeCommand("show definitions")); } else { @@ -610,7 +837,7 @@ public class WorkflowInterpreter { if (command.length == 1) { - out.println("definition: " + ((currentWorkflowDef == null) ? "None" : currentWorkflowDef.id + " , name: " + currentWorkflowDef.title)); + out.println("definition: " + ((currentWorkflowDef == null) ? "None" : currentWorkflowDef.id + " , name: " + currentWorkflowDef.title + " , version: " + currentWorkflowDef.version)); out.println("workflow: " + ((currentPath == null) ? "None" : currentPath.instance.id + " , active: " + currentPath.instance.active)); out.println("path: " + ((currentPath == null) ? "None" : currentPath.id + " , node: " + currentPath.node.title)); } @@ -748,6 +975,17 @@ public class WorkflowInterpreter out.print(interpretCommand("show transitions")); } + else if (command[0].equals("event")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + WorkflowPath path = workflowService.fireEvent(command[1], command[2]); + out.println("event " + command[2] + " fired - path id: " + path.id); + out.print(interpretCommand("show transitions")); + } + else if (command[0].equals("end")) { if (command.length < 3) @@ -769,7 +1007,6 @@ public class WorkflowInterpreter } workflowService.cancelWorkflow(workflowId); out.println("workflow " + workflowId + " cancelled."); - out.print(interpretCommand("show transitions")); } else { @@ -807,7 +1044,7 @@ public class WorkflowInterpreter } if (command[3].equals("imeanit")) { - for (WorkflowDefinition def : workflowService.getDefinitions()) + for (WorkflowDefinition def : workflowService.getAllDefinitions()) { List workflows = workflowService.getActiveWorkflows(def.id); for (WorkflowInstance workflow : workflows) diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index ec17d34c00..d73c464aeb 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -44,6 +44,7 @@ import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -158,7 +159,22 @@ public class WorkflowServiceImpl implements WorkflowService } return Collections.unmodifiableList(definitions); } - + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getAllDefinitions() + */ + public List getAllDefinitions() + { + List definitions = new ArrayList(10); + String[] ids = registry.getWorkflowComponents(); + for (String id: ids) + { + WorkflowComponent component = registry.getWorkflowComponent(id); + definitions.addAll(component.getAllDefinitions()); + } + return Collections.unmodifiableList(definitions); + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitionById(java.lang.String) */ @@ -179,6 +195,16 @@ public class WorkflowServiceImpl implements WorkflowService return component.getDefinitionByName(workflowName); } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getAllDefinitionsByName(java.lang.String) + */ + public List getAllDefinitionsByName(String workflowName) + { + String engineId = BPMEngineRegistry.getEngineId(workflowName); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getAllDefinitionsByName(workflowName); + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitionImage(java.lang.String) */ @@ -243,6 +269,16 @@ public class WorkflowServiceImpl implements WorkflowService return component.getWorkflowPaths(workflowId); } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getPathProperties(java.lang.String) + */ + public Map getPathProperties(String pathId) + { + String engineId = BPMEngineRegistry.getEngineId(pathId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getPathProperties(pathId); + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#cancelWorkflow(java.lang.String) */ @@ -281,6 +317,26 @@ public class WorkflowServiceImpl implements WorkflowService return component.signal(pathId, transition); } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#fireEvent(java.lang.String, java.lang.String) + */ + public WorkflowPath fireEvent(String pathId, String event) + { + String engineId = BPMEngineRegistry.getEngineId(pathId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.fireEvent(pathId, event); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getTimers(java.lang.String) + */ + public List getTimers(String workflowId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowId); + WorkflowComponent component = getWorkflowComponent(engineId); + return component.getTimers(workflowId); + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#getTasksForWorkflowPath(java.lang.String) */ diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index 10bf5ce0b3..badd23884d 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -31,8 +31,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.List; import java.util.Set; import java.util.Map.Entry; import java.util.zip.ZipInputStream; @@ -70,6 +70,7 @@ import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowTaskDefinition; import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.service.cmr.workflow.WorkflowTimer; import org.alfresco.service.cmr.workflow.WorkflowTransition; import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceService; @@ -91,11 +92,14 @@ import org.jbpm.context.exe.VariableInstance; import org.jbpm.db.GraphSession; import org.jbpm.db.TaskMgmtSession; import org.jbpm.file.def.FileDefinition; +import org.jbpm.graph.def.Event; import org.jbpm.graph.def.Node; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.def.Transition; +import org.jbpm.graph.exe.ExecutionContext; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.graph.exe.Token; +import org.jbpm.job.Timer; import org.jbpm.jpdl.par.ProcessArchive; import org.jbpm.jpdl.xml.Problem; import org.jbpm.taskmgmt.def.Task; @@ -139,6 +143,14 @@ public class JBPMEngine extends BPMEngine "where ti.actorId = :actorId " + "and ti.isOpen = false " + "and ti.end is not null"; + + // Note: jBPM query which is not provided out-of-the-box + // TODO: Check jBPMg future and get this implemented in jBPM + private final static String PROCESS_TIMERS_QUERY = + "select timer " + + "from org.jbpm.job.Timer timer " + + "where timer.processInstance = :process "; + // Workflow Path Seperators private final static String WORKFLOW_PATH_SEPERATOR = "-"; @@ -363,6 +375,36 @@ public class JBPMEngine extends BPMEngine } } + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#getDefinitions() + */ + @SuppressWarnings("unchecked") + public List getAllDefinitions() + { + try + { + return (List)jbpmTemplate.execute(new JbpmCallback() + { + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processDefs = (List)graphSession.findAllProcessDefinitions(); + 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) */ @@ -410,7 +452,38 @@ public class JBPMEngine extends BPMEngine throw new WorkflowException("Failed to retrieve workflow definition '" + workflowName + "'", e); } } - + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getAllDefinitionsByName(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getAllDefinitionsByName(final String workflowName) + { + try + { + return (List)jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("synthetic-access") + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + List processDefs = (List)graphSession.findAllProcessDefinitionVersions(createLocalId(workflowName)); + 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 all definitions for workflow '" + workflowName + "'", e); + } + } + /* (non-Javadoc) * @see org.alfresco.repo.workflow.WorkflowComponent#getDefinitionImage(java.lang.String) */ @@ -630,6 +703,55 @@ public class JBPMEngine extends BPMEngine } } + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getPathProperties(java.lang.String) + */ + @SuppressWarnings("unchecked") + public Map getPathProperties(final String pathId) + { + try + { + return (Map) jbpmTemplate.execute(new JbpmCallback() + { + public Map doInJbpm(JbpmContext context) + { + // retrieve jBPM token for workflow position + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + ContextInstance instanceContext = token.getProcessInstance().getContextInstance(); + Map properties = new HashMap(10); + while (token != null) + { + + TokenVariableMap varMap = instanceContext.getTokenVariableMap(token); + if (varMap != null) + { + Map tokenVars = varMap.getVariablesLocally(); + for (Map.Entry entry : tokenVars.entrySet()) + { + String key = entry.getKey(); + QName qname = mapNameToQName(key); + + if (!properties.containsKey(key)) + { + Serializable value = convertValue(entry.getValue()); + properties.put(qname, value); + } + } + } + token = token.getParent(); + } + + return properties; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve properties of path '" + pathId + "'", e); + } + } + /* (non-Javadoc) * @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang.String) */ @@ -739,6 +861,78 @@ public class JBPMEngine extends BPMEngine } } + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#fireEvent(java.lang.String, java.lang.String) + */ + public WorkflowPath fireEvent(final String pathId, final String event) + { + try + { + return (WorkflowPath) jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("unchecked") + public Object doInJbpm(JbpmContext context) + { + // NOTE: Do not allow jBPM built-in events to be fired + if (event.equals(Event.EVENTTYPE_AFTER_SIGNAL) || + event.equals(Event.EVENTTYPE_BEFORE_SIGNAL) || + event.equals(Event.EVENTTYPE_NODE_ENTER) || + event.equals(Event.EVENTTYPE_NODE_LEAVE) || + event.equals(Event.EVENTTYPE_PROCESS_END) || + event.equals(Event.EVENTTYPE_PROCESS_START) || + event.equals(Event.EVENTTYPE_SUBPROCESS_CREATED) || + event.equals(Event.EVENTTYPE_SUBPROCESS_END) || + event.equals(Event.EVENTTYPE_SUPERSTATE_ENTER) || + event.equals(Event.EVENTTYPE_SUPERSTATE_LEAVE) || + event.equals(Event.EVENTTYPE_TASK_ASSIGN) || + event.equals(Event.EVENTTYPE_TASK_CREATE) || + event.equals(Event.EVENTTYPE_TASK_END) || + event.equals(Event.EVENTTYPE_TASK_START) || + event.equals(Event.EVENTTYPE_TIMER) || + event.equals(Event.EVENTTYPE_TRANSITION)) + { + throw new WorkflowException("Event " + event + " is not a valid event"); + } + + // retrieve jBPM token for workflow position + GraphSession graphSession = context.getGraphSession(); + Token token = getWorkflowToken(graphSession, pathId); + + ExecutionContext executionContext = new ExecutionContext(token); + TaskMgmtSession taskSession = context.getTaskMgmtSession(); + List tasks = taskSession.findTaskInstancesByToken(token.getId()); + if (tasks.size() == 0) + { + // fire the event against current node for the token + Node node = token.getNode(); + node.fireEvent(event, executionContext); + } + else + { + // fire the event against tasks associated with the node + // NOTE: this will also propagate the event to the node + for (TaskInstance task : tasks) + { + executionContext.setTaskInstance(task); + task.getTask().fireEvent(event, executionContext); + } + } + + // save + ProcessInstance processInstance = token.getProcessInstance(); + context.save(processInstance); + + // return new workflow path + return createWorkflowPath(token); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to fire event '" + event + "' on workflow path '" + pathId + "'", e); + } + } + /* (non-Javadoc) * @see org.alfresco.repo.workflow.WorkflowComponent#getTasksForWorkflowPath(java.lang.String) */ @@ -772,6 +966,44 @@ public class JBPMEngine extends BPMEngine } } + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getTimers(java.lang.String) + */ + @SuppressWarnings("unchecked") + public List getTimers(final String workflowId) + { + try + { + return (List) jbpmTemplate.execute(new JbpmCallback() + { + public List doInJbpm(JbpmContext context) + { + // retrieve process + GraphSession graphSession = context.getGraphSession(); + ProcessInstance process = getProcessInstance(graphSession, workflowId); + + // retrieve timers for process + Session session = context.getSession(); + Query query = session.createQuery(PROCESS_TIMERS_QUERY); + query.setEntity("process", process); + List timers = query.list(); + + // convert timers to appropriate service response format + List workflowTimers = new ArrayList(timers.size()); + for (Timer timer : timers) + { + WorkflowTimer workflowTimer = createWorkflowTimer(timer); + workflowTimers.add(workflowTimer); + } + return workflowTimers; + } + }); + } + catch(JbpmException e) + { + throw new JbpmException("Couldn't get timers for process '" + workflowId + "'", e); + } + } // // Task Management ... @@ -1562,30 +1794,8 @@ public class JBPMEngine extends BPMEngine // add variable, only if part of task definition or locally defined on task if (taskProperties.containsKey(qname) || taskAssocs.containsKey(qname) || instance.hasVariableLocally(key)) { - Object value = entry.getValue(); - - // - // perform data conversions - // - - // Convert Nodes to NodeRefs - if (value instanceof JBPMNode) - { - value = ((JBPMNode)value).getNodeRef(); - } - else if (value instanceof JBPMNodeList) - { - JBPMNodeList nodes = (JBPMNodeList)value; - List nodeRefs = new ArrayList(nodes.size()); - for (JBPMNode node : nodes) - { - nodeRefs.add(node.getNodeRef()); - } - value = (Serializable)nodeRefs; - } - - // place task variable in map to return - properties.put(qname, (Serializable)value); + Serializable value = convertValue(entry.getValue()); + properties.put(qname, value); } } @@ -1982,6 +2192,45 @@ public class JBPMEngine extends BPMEngine return (missingProps == null) ? null : missingProps.toArray(new QName[missingProps.size()]); } + /** + * Convert a jBPM Value to an Alfresco value + * + * @param value jBPM value + * @return alfresco value + */ + private Serializable convertValue(Object value) + { + Serializable alfValue = null; + + if (value == null) + { + // NOOP + } + else if (value instanceof JBPMNode) + { + alfValue = ((JBPMNode)value).getNodeRef(); + } + else if (value instanceof JBPMNodeList) + { + JBPMNodeList nodes = (JBPMNodeList)value; + List nodeRefs = new ArrayList(nodes.size()); + for (JBPMNode node : nodes) + { + nodeRefs.add(node.getNodeRef()); + } + alfValue = (Serializable)nodeRefs; + } + else if (value instanceof Serializable) + { + alfValue = (Serializable)value; + } + else + { + throw new WorkflowException("Unable to convert jBPM value " + value + " to Alfresco Value - not serializable"); + } + return alfValue; + } + /** * Convert a Repository association to JBPMNodeList or JBPMNode * @@ -2187,15 +2436,7 @@ public class JBPMEngine extends BPMEngine workflowNode.name = node.getName(); workflowNode.title = getLabel(processName + ".node." + workflowNode.name, TITLE_LABEL, workflowNode.name); workflowNode.description = getLabel(processName + ".node." + workflowNode.name, DESC_LABEL, workflowNode.title); - if (node instanceof HibernateProxy) - { - Node realNode = (Node)((HibernateProxy)node).getHibernateLazyInitializer().getImplementation(); - workflowNode.type = realNode.getClass().getSimpleName(); - } - else - { - workflowNode.type = node.getClass().getSimpleName(); - } + workflowNode.type = getRealNode(node).getClass().getSimpleName(); // TODO: Is there a formal way of determing if task node? workflowNode.isTaskNode = workflowNode.type.equals("TaskNode"); List transitions = node.getLeavingTransitions(); @@ -2358,6 +2599,22 @@ public class JBPMEngine extends BPMEngine return deployment; } + /** + * Creates a Workflow Timer + * @param timer jBPM Timer + * @return workflow timer + */ + protected WorkflowTimer createWorkflowTimer(Timer timer) + { + WorkflowTimer workflowTimer = new WorkflowTimer(); + workflowTimer.id = createGlobalId(new Long(timer.getId()).toString()); + workflowTimer.name = timer.getName(); + workflowTimer.dueDate = timer.getDueDate(); + workflowTimer.path = createWorkflowPath(timer.getToken()); + workflowTimer.task = createWorkflowTask(timer.getTaskInstance()); + return workflowTimer; + } + /** * Get the Workflow Task State for the specified JBoss JBPM Task * @@ -2376,4 +2633,23 @@ public class JBPMEngine extends BPMEngine } } + /** + * Helper to retrieve the real jBPM Node + * + * @param node Node + * @return real Node (i.e. the one that's not a Hibernate proxy) + */ + private Node getRealNode(Node node) + { + if (node instanceof HibernateProxy) + { + Node realNode = (Node)((HibernateProxy)node).getHibernateLazyInitializer().getImplementation(); + return realNode; + } + else + { + return node; + } + } + } diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index 029613bf55..c2abc1a912 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -97,13 +97,21 @@ public interface WorkflowService public void undeployDefinition(String workflowDefinitionId); /** - * Gets all deployed Workflow Definitions + * Gets latest deployed Workflow Definitions * - * @return the deployed workflow definitions + * @return the latest deployed workflow definitions */ @Auditable public List getDefinitions(); + /** + * Gets all deployed Workflow Definitions (with all previous versions) + * + * @return the deployed (and previous) workflow definitions + */ + @Auditable + public List getAllDefinitions(); + /** * Gets a Workflow Definition by unique Id * @@ -114,7 +122,7 @@ public interface WorkflowService public WorkflowDefinition getDefinitionById(String workflowDefinitionId); /** - * Gets a Workflow Definition by unique name + * Gets the latest Workflow Definition by unique name * * @param workflowName workflow name e.g. jbpm://review * @return the deployed workflow definition (or null if not found) @@ -122,6 +130,15 @@ public interface WorkflowService @Auditable(parameters = {"workflowName"}) public WorkflowDefinition getDefinitionByName(String workflowName); + /** + * Gets all (including previous) Workflow Definitions for the given unique name + * + * @param workflowName workflow name e.g. jbpm://review + * @return the deployed workflow definition (or null if not found) + */ + @Auditable(parameters = {"workflowName"}) + public List getAllDefinitionsByName(String workflowName); + /** * Gets a graphical view of the Workflow Definition * @@ -184,6 +201,15 @@ public interface WorkflowService @Auditable(parameters = {"workflowId"}) public List getWorkflowPaths(String workflowId); + /** + * Gets the properties associated with the specified path (and parent paths) + * + * @param pathId workflow path id + * @return map of path properties + */ + @Auditable(parameters = {"pathId"}) + public Map getPathProperties(String pathId); + /** * Cancel an "in-fligth" Workflow instance * @@ -215,6 +241,16 @@ public interface WorkflowService @Auditable(parameters = {"pathId", "transitionId"}) public WorkflowPath signal(String pathId, String transitionId); + /** + * Fire custom event against specified path + * + * @param pathId the workflow path to fire event on + * @param event name of event + * @return workflow path (it may have been updated as a result of firing the event + */ + @Auditable(parameters = {"pathId", "event"}) + public WorkflowPath fireEvent(String pathId, String event); + /** * Gets all Tasks associated with the specified path * @@ -223,8 +259,21 @@ public interface WorkflowService */ @Auditable(parameters = {"pathId"}) public List getTasksForWorkflowPath(String pathId); - + + // + // Workflow Timer Management + // + + /** + * Gets all active timers for the specified workflow + * + * @return the list of active timers + */ + @Auditable(parameters = {"workflowId"}) + public List getTimers(String workflowId); + + // // Task Management // @@ -263,7 +312,7 @@ public interface WorkflowService * @param query the filter by which tasks are queried * @return the list of tasks matching the specified query */ - @Auditable(parameters = {"filter"}) + @Auditable(parameters = {"query"}) public List queryTasks(WorkflowTaskQuery query); /** diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowTimer.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowTimer.java new file mode 100644 index 0000000000..0c518ff5a6 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowTimer.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.workflow; + +import java.util.Date; + +public class WorkflowTimer +{ + /** Timer Id */ + public String id; + + /** Transition Name */ + public String name; + + /** Associated Workflow Path */ + public WorkflowPath path; + + /** Associated Workflow Task (if any) */ + public WorkflowTask task; + + /** Due Date */ + public Date dueDate; + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowTimer[id=" + id + ",name=" + name + ",dueDate=" + dueDate + ",path=" + path + ",task=" + task + "]"; + } + +} diff --git a/source/test-resources/jbpmresources/test_customevent.xml b/source/test-resources/jbpmresources/test_customevent.xml new file mode 100644 index 0000000000..394fa8e470 --- /dev/null +++ b/source/test-resources/jbpmresources/test_customevent.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file