diff --git a/config/alfresco/messages/workflow-messages.properties b/config/alfresco/messages/workflow-messages.properties index 99ffea3161..f488826abd 100644 --- a/config/alfresco/messages/workflow-messages.properties +++ b/config/alfresco/messages/workflow-messages.properties @@ -5,7 +5,7 @@ wf_review.workflow.title=Review & Approve wf_review.workflow.description=Send documents for approval wf_review.node.start.title=Start +wf_review.node.start.transition.review.title=Review wf_review.node.review.title=Review -wf_review.node.start.transition.review=Review wf_review.task.wf_submitReviewTask.title=Review wf_workflowmodel.type.wf_submitReviewTask.title=Review diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index cce8c71aa7..e73c1e19fa 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -74,7 +74,7 @@ import org.springframework.util.StringUtils; * * @author Kevin Roast */ -public final class Node implements Serializable, Scopeable +public class Node implements Serializable, Scopeable { private static Log logger = LogFactory.getLog(Node.class); @@ -155,7 +155,6 @@ public final class Node implements Serializable, Scopeable this.nodeService = services.getNodeService(); this.imageResolver = resolver; this.scope = scope; - this.converter = new NodeValueConverter(); } /** @@ -405,7 +404,8 @@ public final class Node implements Serializable, Scopeable Serializable propValue = props.get(qname); // perform the conversion to a script safe value and store - this.properties.put(qname.toString(), converter.convertValueForScript(qname, propValue)); + + this.properties.put(qname.toString(), getValueConverter().convertValueForScript(qname, propValue)); } } @@ -891,14 +891,14 @@ public final class Node implements Serializable, Scopeable */ public void save() { - // persist properties back to the node in the DB + // persist properties back to the node in the DB Map props = new HashMap(getProperties().size()); for (String key : this.properties.keySet()) { Serializable value = (Serializable)this.properties.get(key); // perform the conversion from script wrapper object to repo serializable values - value = converter.convertValueForRepo(value); + value = getValueConverter().convertValueForRepo(value); props.put(createQName(key), value); } @@ -1209,7 +1209,7 @@ public final class Node implements Serializable, Scopeable { // get the value out for the specified key - make sure it is Serializable Object value = props.get((String)propId, props); - value = converter.convertValueForRepo((Serializable)value); + value = getValueConverter().convertValueForRepo((Serializable)value); aspectProps.put(createQName((String)propId), (Serializable)value); } } @@ -1635,12 +1635,37 @@ public final class Node implements Serializable, Scopeable // ------------------------------------------------------------------------------ // Value Conversion + + /** + * Gets the node value converter + * + * @return the node value converter + */ + protected NodeValueConverter getValueConverter() + { + if (converter == null) + { + converter = createValueConverter(); + } + return converter; + } + + + /** + * Constructs the node value converter + * + * @return the node value converter + */ + protected NodeValueConverter createValueConverter() + { + return new NodeValueConverter(); + } /** * Value converter with knowledge of Node specific value types */ - private final class NodeValueConverter extends ValueConverter + public class NodeValueConverter extends ValueConverter { /** * Convert an object from any repository serialized value to a valid script object. diff --git a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java index e64de75a45..3ebe7fc26d 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowComponent.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowComponent.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import org.alfresco.service.cmr.workflow.WorkflowDefinition; +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; @@ -46,9 +47,9 @@ public interface WorkflowComponent * * @param workflowDefinition the content object containing the definition * @param mimetype (optional) the mime type of the workflow definition - * @return workflow definition + * @return workflow deployment descriptor */ - public WorkflowDefinition deployDefinition(InputStream workflowDefinition, String mimetype); + public WorkflowDeployment deployDefinition(InputStream workflowDefinition, String mimetype); /** * Is the specified Workflow Definition already deployed? diff --git a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java index 5998437aeb..ac101d87f0 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowDeployer.java @@ -24,7 +24,7 @@ import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.cmr.view.ImporterException; -import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.transaction.TransactionService; @@ -148,17 +148,13 @@ public class WorkflowDeployer implements ApplicationListener if (workflowService.isDefinitionDeployed(engineId, workflowResource.getInputStream(), mimetype)) { if (logger.isDebugEnabled()) - { logger.debug("Workflow deployer: Definition '" + location + "' already deployed"); - } } else { - WorkflowDefinition def = workflowService.deployDefinition(engineId, workflowResource.getInputStream(), mimetype); + WorkflowDeployment deployment = workflowService.deployDefinition(engineId, workflowResource.getInputStream(), mimetype); if (logger.isInfoEnabled()) - { - logger.info("Workflow deployer: Deployed process definition '" + def.title + "' (version " + def.version + ") from '" + location + "'"); - } + logger.info("Workflow deployer: Deployed process definition '" + deployment.definition.title + "' (version " + deployment.definition.version + ") from '" + location + "' with " + deployment.problems.length + " problems"); } } } diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 770f8b2a0f..64ac955e93 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -25,6 +25,7 @@ import java.util.Map; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowPath; @@ -32,6 +33,8 @@ 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; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** @@ -42,6 +45,10 @@ import org.alfresco.service.namespace.QName; */ public class WorkflowServiceImpl implements WorkflowService { + // Logging support + private static Log logger = LogFactory.getLog("org.alfresco.repo.workflow"); + + // Dependent services private BPMEngineRegistry registry; private WorkflowPackageComponent workflowPackageComponent; @@ -70,10 +77,20 @@ public class WorkflowServiceImpl implements WorkflowService /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#deployDefinition(java.lang.String, java.io.InputStream, java.lang.String) */ - public WorkflowDefinition deployDefinition(String engineId, InputStream workflowDefinition, String mimetype) + public WorkflowDeployment deployDefinition(String engineId, InputStream workflowDefinition, String mimetype) { WorkflowComponent component = getWorkflowComponent(engineId); - return component.deployDefinition(workflowDefinition, mimetype); + WorkflowDeployment deployment = component.deployDefinition(workflowDefinition, mimetype); + + if (logger.isDebugEnabled() && deployment.problems.length > 0) + { + for (String problem : deployment.problems) + { + logger.debug("Workflow definition '" + deployment.definition.title + "' problem: " + problem); + } + } + + return deployment; } /* (non-Javadoc) @@ -88,7 +105,7 @@ public class WorkflowServiceImpl implements WorkflowService /* (non-Javadoc) * @see org.alfresco.service.cmr.workflow.WorkflowService#deployDefinition(org.alfresco.service.cmr.repository.NodeRef) */ - public WorkflowDefinition deployDefinition(NodeRef definitionContent) + public WorkflowDeployment deployDefinition(NodeRef definitionContent) { // TODO throw new UnsupportedOperationException(); diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java index cc34a31a1c..51275d733b 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngine.java @@ -46,6 +46,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowNode; @@ -68,11 +69,15 @@ 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.jpdl.par.ProcessArchive; +import org.jbpm.jpdl.xml.JpdlXmlReader; +import org.jbpm.jpdl.xml.Problem; import org.jbpm.taskmgmt.def.Task; import org.jbpm.taskmgmt.exe.TaskInstance; import org.springframework.util.StringUtils; import org.springmodules.workflow.jbpm31.JbpmCallback; import org.springmodules.workflow.jbpm31.JbpmTemplate; +import org.xml.sax.InputSource; /** @@ -178,23 +183,23 @@ public class JBPMEngine extends BPMEngine /* (non-Javadoc) * @see org.alfresco.repo.workflow.WorkflowDefinitionComponent#deployDefinition(java.io.InputStream) */ - public WorkflowDefinition deployDefinition(final InputStream workflowDefinition, final String mimetype) + public WorkflowDeployment deployDefinition(final InputStream workflowDefinition, final String mimetype) { try { - return (WorkflowDefinition)jbpmTemplate.execute(new JbpmCallback() + return (WorkflowDeployment)jbpmTemplate.execute(new JbpmCallback() { public Object doInJbpm(JbpmContext context) { // construct process definition - ProcessDefinition def = createProcessDefinition(workflowDefinition, mimetype); + CompiledProcessDefinition compiledDef = compileProcessDefinition(workflowDefinition, mimetype); // deploy the parsed definition - context.deployProcessDefinition(def); + context.deployProcessDefinition(compiledDef.def); // return deployed definition - WorkflowDefinition workflowDef = createWorkflowDefinition(def); - return workflowDef; + WorkflowDeployment workflowDeployment = createWorkflowDeployment(compiledDef); + return workflowDeployment; } }); } @@ -217,11 +222,11 @@ public class JBPMEngine extends BPMEngine public Boolean doInJbpm(JbpmContext context) { // create process definition from input stream - ProcessDefinition processDefinition = createProcessDefinition(workflowDefinition, mimetype); + CompiledProcessDefinition processDefinition = compileProcessDefinition(workflowDefinition, mimetype); // retrieve process definition from Alfresco Repository GraphSession graphSession = context.getGraphSession(); - ProcessDefinition existingDefinition = graphSession.findLatestProcessDefinition(processDefinition.getName()); + ProcessDefinition existingDefinition = graphSession.findLatestProcessDefinition(processDefinition.def.getName()); return (existingDefinition == null) ? false : true; } }); @@ -828,6 +833,53 @@ public class JBPMEngine extends BPMEngine // Helpers... // + + /** + * Process Definition with accompanying problems + */ + private static class CompiledProcessDefinition + { + public CompiledProcessDefinition(ProcessDefinition def, List problems) + { + this.def = def; + this.problems = new String[problems.size()]; + int i = 0; + for (Problem problem : problems) + { + this.problems[i++] = problem.toString(); + } + } + + protected ProcessDefinition def; + protected String[] problems; + } + + /** + * JpdlXmlReader with access to problems encountered during compile. + * + * @author davidc + */ + private static class JBPMEngineJpdlXmlReader extends JpdlXmlReader + { + private static final long serialVersionUID = -753730152120696221L; + + public JBPMEngineJpdlXmlReader(InputStream inputStream) + { + super(new InputSource(inputStream)); + } + + /** + * Gets the problems + * + * @return problems + */ + public List getProblems() + { + return problems; + } + } + + /** * Construct a Process Definition from the provided Process Definition stream * @@ -835,10 +887,11 @@ public class JBPMEngine extends BPMEngine * @param mimetype mimetype of stream * @return process definition */ - protected ProcessDefinition createProcessDefinition(InputStream definitionStream, String mimetype) + @SuppressWarnings("unchecked") + protected CompiledProcessDefinition compileProcessDefinition(InputStream definitionStream, String mimetype) { String actualMimetype = (mimetype == null) ? MimetypeMap.MIMETYPE_ZIP : mimetype; - ProcessDefinition def = null; + CompiledProcessDefinition compiledDef = null; // parse process definition from jBPM process archive file @@ -848,7 +901,9 @@ public class JBPMEngine extends BPMEngine try { zipInputStream = new ZipInputStream(definitionStream); - def = ProcessDefinition.parseParZipInputStream(zipInputStream); + ProcessArchive reader = new ProcessArchive(zipInputStream); + ProcessDefinition def = reader.parseProcessDefinition(); + compiledDef = new CompiledProcessDefinition(def, reader.getProblems()); } catch(Exception e) { @@ -869,15 +924,17 @@ public class JBPMEngine extends BPMEngine { try { - def = ProcessDefinition.parseXmlInputStream(definitionStream); + JBPMEngineJpdlXmlReader jpdlReader = new JBPMEngineJpdlXmlReader(definitionStream); + ProcessDefinition def = jpdlReader.readProcessDefinition(); + compiledDef = new CompiledProcessDefinition(def, jpdlReader.getProblems()); } catch(Exception e) { throw new JbpmException("Failed to parse process definition from jBPM xml stream", e); } } - - return def; + + return compiledDef; } /** @@ -1152,12 +1209,13 @@ public class JBPMEngine extends BPMEngine String name = key.toPrefixString(this.namespaceService); if (value instanceof NodeRef) { - value = new org.alfresco.repo.jscript.Node((NodeRef)value, serviceRegistry, null); + value = new JBPMNode((NodeRef)value, serviceRegistry); } instance.setVariableLocally(name, value); } } + /** * Convert a list of Alfresco Authorities to a list of authority Names * @@ -1391,6 +1449,20 @@ public class JBPMEngine extends BPMEngine return taskDef; } + /** + * Creates a Workflow Deployment + * + * @param compiledDef compiled JBPM process definition + * @return workflow deployment + */ + protected WorkflowDeployment createWorkflowDeployment(CompiledProcessDefinition compiledDef) + { + WorkflowDeployment deployment = new WorkflowDeployment(); + deployment.definition = createWorkflowDefinition(compiledDef.def); + deployment.problems = compiledDef.problems; + return deployment; + } + /** * Get the Workflow Task State for the specified JBoss JBPM Task * diff --git a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java index 7f6a506c37..93146132b5 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/JBPMEngineTest.java @@ -35,6 +35,7 @@ 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.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowPath; @@ -72,7 +73,8 @@ public class JBPMEngineTest extends BaseSpringTest // deploy test process definition ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_processdefinition.xml"); assertFalse(workflowComponent.isDefinitionDeployed(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML)); - testWorkflowDef = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + testWorkflowDef = deployment.definition; assertNotNull(testWorkflowDef); assertEquals("Test", testWorkflowDef.title); assertEquals("1", testWorkflowDef.version); @@ -95,7 +97,8 @@ public class JBPMEngineTest extends BaseSpringTest public void testDeployWorkflow() throws Exception { ClassPathResource processDef = new ClassPathResource("org/alfresco/repo/workflow/jbpm/test_processdefinition.xml"); - testWorkflowDef = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + WorkflowDeployment deployment = workflowComponent.deployDefinition(processDef.getInputStream(), MimetypeMap.MIMETYPE_XML); + testWorkflowDef = deployment.definition; assertNotNull(testWorkflowDef); assertEquals("Test", testWorkflowDef.title); assertEquals("2", testWorkflowDef.version); diff --git a/source/java/org/alfresco/repo/workflow/jbpm/NodeConverter.java b/source/java/org/alfresco/repo/workflow/jbpm/NodeConverter.java index 42665f779a..8f74a775b7 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/NodeConverter.java +++ b/source/java/org/alfresco/repo/workflow/jbpm/NodeConverter.java @@ -16,7 +16,6 @@ */ package org.alfresco.repo.workflow.jbpm; -import org.alfresco.repo.jscript.Node; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.jbpm.context.exe.Converter; @@ -46,7 +45,7 @@ public class NodeConverter implements Converter { return true; } - return (value.getClass() == Node.class); + return (value.getClass() == JBPMNode.class); } /* (non-Javadoc) @@ -57,7 +56,7 @@ public class NodeConverter implements Converter Object converted = null; if (o != null) { - converted = ((Node)o).getNodeRef().toString(); + converted = ((JBPMNode)o).getNodeRef().toString(); } return converted; } @@ -72,7 +71,7 @@ public class NodeConverter implements Converter { BeanFactoryReference factory = jbpmFactoryLocator.useBeanFactory(null); ServiceRegistry serviceRegistry = (ServiceRegistry)factory.getFactory().getBean(ServiceRegistry.SERVICE_REGISTRY); - reverted = new Node(new NodeRef((String)o), serviceRegistry, null); + reverted = new JBPMNode(new NodeRef((String)o), serviceRegistry); } return reverted; } diff --git a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml index 2fc5dd3e4a..fa44ad9758 100644 --- a/source/java/org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml +++ b/source/java/org/alfresco/repo/workflow/jbpm/jbpm.varmapping.xml @@ -123,7 +123,7 @@ - + diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java index 9ba617184d..fd3d55165d 100644 --- a/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowService.java @@ -43,11 +43,22 @@ public interface WorkflowService * * @param engineId the bpm engine id * @param workflowDefinition the workflow definition - * @param overwrite true => redeploy, if process definition already exists - * @return mimetype the mimetype of the workflow definition + * @param mimetype the mimetype of the workflow definition + * @return workflow deployment descriptor */ - public WorkflowDefinition deployDefinition(String engineId, InputStream workflowDefinition, String mimetype); + public WorkflowDeployment deployDefinition(String engineId, InputStream workflowDefinition, String mimetype); + /** + * 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 deployment descriptor + */ + public WorkflowDeployment deployDefinition(NodeRef workflowDefinition); + /** * Is the specified Workflow Definition already deployed? * @@ -61,17 +72,6 @@ public interface WorkflowService */ public boolean isDefinitionDeployed(String engineId, InputStream workflowDefinition, String mimetype); - /** - * 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 *