diff --git a/config/alfresco/messages/workflow-interpreter-help.txt b/config/alfresco/messages/workflow-interpreter-help.txt index d8ed5405e6..d38c52affb 100644 --- a/config/alfresco/messages/workflow-interpreter-help.txt +++ b/config/alfresco/messages/workflow-interpreter-help.txt @@ -23,6 +23,12 @@ ok> use ## Workflow Definition Commands ## +ok> show file + + Output the contents of the file located at . + + class path to workflow definition file. + ok> deploy Deploy workflow definition to Alfresco server. @@ -61,8 +67,8 @@ ok> var [*]= e.g. - set bpm:assignee*=admin,fred - set wf:notifyMe=true + var bpm:assignee*=admin,fred + var wf:notifyMe=true ok> var [*] person @@ -74,7 +80,17 @@ ok> var [*] person e.g. - set bpm:assignee* person admin,fred + var bpm:assignee* person admin,fred + +ok> var package + + Define or update a (bpm:workflowPackage) node ref variable. + + A new workflow package is created containing content items. + + e.g. + + var bpm:package package 4 ok> var = @@ -129,6 +145,10 @@ ok> end workflow End (cancel) the specified . +ok> delete workflow + + Force deletion of the specified . + ## ## Task Commands ## diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index c982c5883d..641750714b 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -34,6 +34,7 @@ + @@ -87,6 +88,8 @@ + ${spaces.store} + /${spaces.company_home.childname} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java index 15aebb5ec5..86d621b116 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java @@ -21,6 +21,7 @@ import java.io.Serializable; import java.util.List; import java.util.Map; +import org.alfresco.service.Auditable; import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowInstance; @@ -95,6 +96,15 @@ public interface WorkflowComponent */ public WorkflowDefinition getDefinitionByName(String workflowName); + /** + * Gets a graphical view of the Workflow Definition + * + * @param workflowDefinitionId the workflow definition id + * @return graphical image of workflow definition + */ + @Auditable(parameters = {"workflowDefinitionId"}) + public byte[] getDefinitionImage(String workflowDefinitionId); + // // Workflow Instance Support @@ -142,6 +152,14 @@ public interface WorkflowComponent */ public WorkflowInstance cancelWorkflow(String workflowId); + /** + * Delete an "in-fligth" Workflow instance + * + * @param workflowId the workflow instance to cancel + * @return an updated representation of the workflow instance + */ + public WorkflowInstance deleteWorkflow(String workflowId); + /** * Signal the transition from one Workflow Node to another within an "in-flight" * process. diff --git a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java index f16e0e14b8..c30639dbce 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowInterpreter.java @@ -30,12 +30,16 @@ import java.util.Map; import org.alfresco.i18n.I18NUtil; import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; 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.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PersonService; @@ -68,6 +72,8 @@ public class WorkflowInterpreter private AVMService avmService; private AVMSyncService avmSyncService; private PersonService personService; + private FileFolderService fileFolderService; + /** * The reader for interaction. @@ -162,6 +168,14 @@ public class WorkflowInterpreter this.personService = personService; } + /** + * @param fileFolderService fileFolderService + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + /** * A Read-Eval-Print loop. */ @@ -250,6 +264,31 @@ public class WorkflowInterpreter return "Syntax Error.\n"; } + else if (command[1].equals("file")) + { + if (command.length != 3) + { + return "Syntax Error.\n"; + } + ClassPathResource file = new ClassPathResource(command[2]); + InputStream fileStream = file.getInputStream(); + byte[] fileBytes = new byte[500]; + try + { + int read = fileStream.read(fileBytes); + while (read != -1) + { + bout.write(fileBytes, 0, read); + read = fileStream.read(fileBytes); + } + } + finally + { + fileStream.close(); + } + out.println(); + } + else if (command[1].equals("definitions")) { List defs = workflowService.getDefinitions(); @@ -609,7 +648,29 @@ public class WorkflowInterpreter return "Syntax Error.\n"; } } - + + else if (command[0].equals("delete")) + { + if (command.length < 3) + { + return "Syntax Error.\n"; + } + else if (command[1].equals("workflow")) + { + String workflowId = (command.length == 3) ? command[2] : (currentPath == null) ? null : currentPath.instance.id; + if (workflowId == null) + { + return "Syntax Error. Workflow Id not specified.\n"; + } + workflowService.deleteWorkflow(workflowId); + out.println("workflow " + workflowId + " deleted."); + } + else + { + return "Syntax Error.\n"; + } + } + else if (command[0].equals("var")) { if (command.length == 1) @@ -699,7 +760,6 @@ public class WorkflowInterpreter } out.println("set var " + qname + " = " + vars.get(qname)); } - else if (command[2].equals("avmpackage")) { // lookup source folder of changes @@ -741,7 +801,20 @@ public class WorkflowInterpreter vars.put(qname, packageNodeRef); out.println("set var " + qname + " = " + vars.get(qname)); } - + else if (command[2].equals("package")) + { + QName qname = QName.createQName(command[1], namespaceService); + int number = new Integer(command[3]); + NodeRef packageNodeRef = workflowService.createPackage(null); + for (int i = 0; i < number; i++) + { + FileInfo fileInfo = fileFolderService.create(packageNodeRef, "Content" + i, ContentModel.TYPE_CONTENT); + ContentWriter writer = fileFolderService.getWriter(fileInfo.getNodeRef()); + writer.putContent("Content" + i); + } + vars.put(qname, packageNodeRef); + out.println("set var " + qname + " = " + vars.get(qname)); + } else { return "Syntax Error.\n"; @@ -784,5 +857,5 @@ public class WorkflowInterpreter { return AuthenticationUtil.getCurrentUserName(); } - + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index f290514867..66860e7412 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -155,6 +155,21 @@ public class WorkflowServiceImpl implements WorkflowService WorkflowComponent component = getWorkflowComponent(engineId); return component.getDefinitionByName(workflowName); } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#getDefinitionImage(java.lang.String) + */ + public byte[] getDefinitionImage(String workflowDefinitionId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowDefinitionId); + WorkflowComponent component = getWorkflowComponent(engineId); + byte[] definitionImage = component.getDefinitionImage(workflowDefinitionId); + if (definitionImage == null) + { + definitionImage = new byte[0]; + } + return definitionImage; + } /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#startWorkflow(java.lang.String, java.util.Map) @@ -217,6 +232,20 @@ public class WorkflowServiceImpl implements WorkflowService return component.cancelWorkflow(workflowId); } + /* (non-Javadoc) + * @see org.alfresco.service.cmr.workflow.WorkflowService#deleteWorkflow(java.lang.String) + */ + public WorkflowInstance deleteWorkflow(String workflowId) + { + String engineId = BPMEngineRegistry.getEngineId(workflowId); + WorkflowComponent component = getWorkflowComponent(engineId); + WorkflowInstance instance = component.deleteWorkflow(workflowId); + // NOTE: Delete workflow package after deleting workflow, so it's still available + // in process-end events of workflow definition + workflowPackageComponent.deletePackage(instance.workflowPackage); + return instance; + } + /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#signal(java.lang.String, 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 70932b3a91..66e0168323 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -47,6 +47,7 @@ import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; @@ -71,6 +72,7 @@ import org.jbpm.context.exe.ContextInstance; import org.jbpm.context.exe.TokenVariableMap; import org.jbpm.db.GraphSession; import org.jbpm.db.TaskMgmtSession; +import org.jbpm.file.def.FileDefinition; import org.jbpm.graph.def.Node; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.def.Transition; @@ -106,6 +108,10 @@ public class JBPMEngine extends BPMEngine protected ServiceRegistry serviceRegistry; protected PersonService personService; protected JbpmTemplate jbpmTemplate; + + // Company Home + protected StoreRef companyHomeStore; + protected String companyHomePath; // Note: jBPM query which is not provided out-of-the-box // TODO: Check jBPM 3.2 and get this implemented in jBPM @@ -186,6 +192,26 @@ public class JBPMEngine extends BPMEngine this.serviceRegistry = serviceRegistry; } + /** + * Sets the Company Home Path + * + * @param companyHomePath + */ + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /** + * Sets the Company Home Store + * + * @param companyHomeStore + */ + public void setCompanyHomeStore(String companyHomeStore) + { + this.companyHomeStore = new StoreRef(companyHomeStore); + } + // // Workflow Definition... @@ -357,6 +383,31 @@ public class JBPMEngine extends BPMEngine } } + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#getDefinitionImage(java.lang.String) + */ + public byte[] getDefinitionImage(final String workflowDefinitionId) + { + try + { + return (byte[])jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("synthetic-access") + public Object doInJbpm(JbpmContext context) + { + GraphSession graphSession = context.getGraphSession(); + ProcessDefinition processDefinition = getProcessDefinition(graphSession, workflowDefinitionId); + FileDefinition fileDefinition = processDefinition.getFileDefinition(); + return (fileDefinition == null) ? null : fileDefinition.getBytes("processimage.jpg"); + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to retrieve workflow definition image for '" + workflowDefinitionId + "'", e); + } + } + /** * Gets a jBPM process definition * @@ -404,10 +455,18 @@ public class JBPMEngine extends BPMEngine // assign initial process context ContextInstance processContext = processInstance.getContextInstance(); + processContext.setVariable("cancelled", false); + NodeRef companyHome = getCompanyHome(); + processContext.setVariable("companyhome", new JBPMNode(companyHome, serviceRegistry)); NodeRef initiatorPerson = mapNameToAuthority(currentUserName); if (initiatorPerson != null) { processContext.setVariable("initiator", new JBPMNode(initiatorPerson, serviceRegistry)); + NodeRef initiatorHome = (NodeRef)nodeService.getProperty(initiatorPerson, ContentModel.PROP_HOMEFOLDER); + if (initiatorHome != null) + { + processContext.setVariable("initiatorhome", new JBPMNode(initiatorHome, serviceRegistry)); + } } // create the start task if one exists @@ -558,10 +617,41 @@ public class JBPMEngine extends BPMEngine // retrieve and cancel process instance GraphSession graphSession = context.getGraphSession(); ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); + processInstance.getContextInstance().setVariable("cancelled", true); + processInstance.end(); // TODO: Determine if this is the most appropriate way to cancel workflow... // It might be useful to record point at which it was cancelled etc WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); + // delete the process instance + graphSession.deleteProcessInstance(processInstance, true, true, true); + return workflowInstance; + } + }); + } + catch(JbpmException e) + { + throw new WorkflowException("Failed to cancel workflow instance '" + workflowId + "'", e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.WorkflowComponent#cancelWorkflow(java.lang.String) + */ + public WorkflowInstance deleteWorkflow(final String workflowId) + { + try + { + return (WorkflowInstance) jbpmTemplate.execute(new JbpmCallback() + { + @SuppressWarnings("unchecked") + public Object doInJbpm(JbpmContext context) + { + // retrieve and cancel process instance + GraphSession graphSession = context.getGraphSession(); + ProcessInstance processInstance = getProcessInstance(graphSession, workflowId); + WorkflowInstance workflowInstance = createWorkflowInstance(processInstance); + // delete the process instance graphSession.deleteProcessInstance(processInstance, true, true, true); workflowInstance.active = false; @@ -1755,7 +1845,22 @@ public class JBPMEngine extends BPMEngine return (label == null) ? defaultLabel : label; } - + /** + * Gets the Company Home + * + * @return company home node ref + */ + private NodeRef getCompanyHome() + { + // TODO: Determine if caching is required + List refs = serviceRegistry.getSearchService().selectNodes(nodeService.getRootNode(companyHomeStore), companyHomePath, null, namespaceService, false); + if (refs.size() != 1) + { + throw new IllegalStateException("Invalid company home path: " + companyHomePath + " - found: " + refs.size()); + } + return refs.get(0); + } + // // Workflow Data Object Creation... // @@ -1888,7 +1993,7 @@ public class JBPMEngine extends BPMEngine final String title = getLabel(name + ".workflow", TITLE_LABEL, name); final String description = getLabel(name + ".workflow", DESC_LABEL, title); return new WorkflowDefinition(createGlobalId(new Long(definition.getId()).toString()), - name, + createGlobalId(name), new Integer(definition.getVersion()).toString(), title, description, diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java index f85d55f7c4..f0df6252e1 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java @@ -87,7 +87,7 @@ public class JBPMEngineTest extends BaseSpringTest WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); testWorkflowDef = deployment.definition; assertNotNull(testWorkflowDef); - assertEquals("test", testWorkflowDef.name); + assertEquals("jbpm$test", testWorkflowDef.name); assertEquals("1", testWorkflowDef.version); assertTrue(workflowComponent.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); @@ -119,7 +119,7 @@ public class JBPMEngineTest extends BaseSpringTest WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); testWorkflowDef = deployment.definition; assertNotNull(testWorkflowDef); - assertEquals("test", testWorkflowDef.name); + assertEquals("jbpm$test", testWorkflowDef.name); assertEquals("2", testWorkflowDef.version); } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/test_processdefinition.xml b/source/java/org/alfresco/repo/workflow/jbpm/test_processdefinition.xml index f05428d4e6..2888198b1a 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/test_processdefinition.xml +++ b/source/java/org/alfresco/repo/workflow/jbpm/test_processdefinition.xml @@ -34,6 +34,8 @@ + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java index 4c970679e6..17f6bc1ac4 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowDefinition.java @@ -84,11 +84,11 @@ public class WorkflowDefinition return this.startTaskDefinition; } - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - public String toString() - { - return "WorkflowDefinition[id=" + id + ",version=" + version + ",title=" + title + ",startTask=" + startTaskDefinition.toString() + "]"; - } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() + { + return "WorkflowDefinition[id=" + id + ",name=" + name + ",version=" + version + ",title=" + title + ",startTask=" + ((startTaskDefinition == null) ? "undefined" : startTaskDefinition.toString()) + "]"; + } } diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index 3a5dd4c3b2..f5dfbb14f3 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -114,6 +114,15 @@ public interface WorkflowService @Auditable(parameters = {"workflowName"}) public WorkflowDefinition getDefinitionByName(String workflowName); + /** + * Gets a graphical view of the Workflow Definition + * + * @param workflowDefinitionId the workflow definition id + * @return image view of the workflow definition + */ + @Auditable(parameters = {"workflowDefinitionId"}) + public byte[] getDefinitionImage(String workflowDefinitionId); + // // Workflow Instance Management @@ -176,6 +185,18 @@ public interface WorkflowService @Auditable(parameters = {"workflowId"}) public WorkflowInstance cancelWorkflow(String workflowId); + /** + * Delete an "in-fligth" Workflow instance + * + * NOTE: This will force a delete, meaning that the workflow instance may not + * go through all the appropriate cancel events. + * + * @param workflowId the workflow instance to cancel + * @return an updated representation of the workflow instance + */ + @Auditable(parameters = {"workflowId"}) + public WorkflowInstance deleteWorkflow(String workflowId); + /** * Signal the transition from one Workflow Node to another *