From afade3375c457850460abebb98b9518ea166c940 Mon Sep 17 00:00:00 2001 From: Joram Barrez Date: Fri, 4 Jan 2013 18:57:02 +0100 Subject: [PATCH] ACT-1518: first cut of generic bits for kickstart --- modules/activiti-simple-workflow/pom.xml | 104 ++++ .../BaseStepDefinitionConverter.java | 81 ++++ .../simple/converter/ConversionConstants.java | 20 + ...tWorkflowDefinitionConversionListener.java | 81 ++++ .../HumanStepDefinitionConverter.java | 66 +++ .../converter/StepDefinitionConverter.java | 49 ++ .../WorkflowDefinitionConversion.java | 175 +++++++ .../WorkflowDefinitionConversionFactory.java | 79 +++ .../WorkflowDefinitionConversionListener.java | 24 + .../simple/definition/FormDefinition.java | 47 ++ .../definition/FormPropertyDefinition.java | 50 ++ .../definition/HumanStepDefinition.java | 71 +++ .../simple/definition/StepDefinition.java | 48 ++ .../definition/StepDefinitionContainer.java | 27 ++ .../simple/definition/WorkflowDefinition.java | 131 +++++ .../simple/diagram/WorkflowDIGenerator.java | 451 ++++++++++++++++++ .../simple/WorkflowConversionTest.java | 182 +++++++ .../src/test/resources/activiti.cfg.xml | 25 + .../src/test/resources/log4j.properties | 10 + pom.xml | 1 + userguide/src/en/chapters/ch06-Deployment.xml | 2 +- 21 files changed, 1723 insertions(+), 1 deletion(-) create mode 100644 modules/activiti-simple-workflow/pom.xml create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/BaseStepDefinitionConverter.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/ConversionConstants.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/DefaultWorkflowDefinitionConversionListener.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/HumanStepDefinitionConverter.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/StepDefinitionConverter.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversion.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionFactory.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionListener.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormDefinition.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormPropertyDefinition.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/HumanStepDefinition.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinition.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinitionContainer.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/WorkflowDefinition.java create mode 100644 modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/diagram/WorkflowDIGenerator.java create mode 100644 modules/activiti-simple-workflow/src/test/java/org/activiti/workflow/simple/WorkflowConversionTest.java create mode 100644 modules/activiti-simple-workflow/src/test/resources/activiti.cfg.xml create mode 100644 modules/activiti-simple-workflow/src/test/resources/log4j.properties diff --git a/modules/activiti-simple-workflow/pom.xml b/modules/activiti-simple-workflow/pom.xml new file mode 100644 index 0000000000..118d5030d4 --- /dev/null +++ b/modules/activiti-simple-workflow/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + Activiti - Simple Workflow + activiti-simple-workflow + + + org.activiti + activiti-root + ../.. + 5.12-SNAPSHOT + + + + + + org.activiti + activiti-bpmn-model + + + org.activiti + activiti-bpmn-converter + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-mapper-asl + + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + + + org.slf4j + slf4j-log4j12 + test + + + + junit + junit + test + + + org.activiti + activiti-engine + test + + + com.h2database + h2 + test + + + + + + distro + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.1 + + + attach-sources + package + + jar-no-fork + + + + + + + + + + + + + activiti + Activiti + https://maven.alfresco.com/nexus/content/repositories/activiti/ + + + Alfresco thirdparty + https://maven.alfresco.com/nexus/content/repositories/thirdparty/ + + + + diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/BaseStepDefinitionConverter.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/BaseStepDefinitionConverter.java new file mode 100644 index 0000000000..09eaa07a40 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/BaseStepDefinitionConverter.java @@ -0,0 +1,81 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + +import org.activiti.bpmn.model.FlowElement; +import org.activiti.bpmn.model.SequenceFlow; +import org.activiti.workflow.simple.definition.StepDefinition; + +/** + * Base class that can be used for {@link StepDefinitionConverter}, contains + * utility-methods. + * + * @author Frederik Heremans + * @author Joram Barrez + */ +public abstract class BaseStepDefinitionConverter implements StepDefinitionConverter { + + @SuppressWarnings("unchecked") + public void convertStepDefinition(StepDefinition stepDefinition, WorkflowDefinitionConversion conversion) { + U typedStepDefinition = (U) stepDefinition; + T processArtifact = createProcessArtifact(typedStepDefinition, conversion); + createAdditionalArtifacts(processArtifact); + } + + /** + * Subclasses must implement this method and create the BPMN 2.0 process artifact(s) for the provided step. + */ + protected abstract T createProcessArtifact(U stepDefinition, WorkflowDefinitionConversion conversion); + + /** + * Subclasses should override this method if they want to create additional artifacts + * for this specific step. The default generated process artifact is passed as parameter. + */ + protected void createAdditionalArtifacts(T defaultGeneratedArtifact) { + } + + protected void addFlowElement(WorkflowDefinitionConversion conversion, FlowElement flowElement) { + addFlowElement(conversion, flowElement, true); + } + + protected void addFlowElement(WorkflowDefinitionConversion conversion, FlowElement flowElement, boolean addSequenceFlow) { + if (addSequenceFlow) { + addSequenceFlow(conversion, conversion.getLastActivityId(), flowElement.getId()); + } + conversion.getProcess().addFlowElement(flowElement); + conversion.setLastActivityId(flowElement.getId()); + } + + /** + * Add a sequence-flow to the current process from source to target. + * Sequence-flow name is set to a user-friendly name, containing an + * incrementing number. + * + * @param sourceActivityId + * @param targetActivityId + */ + public void addSequenceFlow(WorkflowDefinitionConversion conversion, String sourceActivityId, String targetActivityId) { + SequenceFlow sequenceFlow = new SequenceFlow(); + sequenceFlow.setId(conversion.getUniqueNumberedId(getSequenceFlowPrefix())); + sequenceFlow.setSourceRef(sourceActivityId); + sequenceFlow.setTargetRef(targetActivityId); + + conversion.getProcess().addFlowElement(sequenceFlow); + } + + // Subclasses can overwrite this if they want a different sequence flow prefix + protected String getSequenceFlowPrefix() { + return ConversionConstants.DEFAULT_SEQUENCEFLOW_PREFIX; + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/ConversionConstants.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/ConversionConstants.java new file mode 100644 index 0000000000..929983e9a8 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/ConversionConstants.java @@ -0,0 +1,20 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + + +public interface ConversionConstants { + + String DEFAULT_SEQUENCEFLOW_PREFIX = "sequenceFlow"; + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/DefaultWorkflowDefinitionConversionListener.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/DefaultWorkflowDefinitionConversionListener.java new file mode 100644 index 0000000000..d450699922 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/DefaultWorkflowDefinitionConversionListener.java @@ -0,0 +1,81 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + +import org.activiti.bpmn.model.EndEvent; +import org.activiti.bpmn.model.Process; +import org.activiti.bpmn.model.SequenceFlow; +import org.activiti.bpmn.model.StartEvent; +import org.activiti.workflow.simple.definition.WorkflowDefinition; + +/** + * @author Joram Barrez + */ +public class DefaultWorkflowDefinitionConversionListener implements WorkflowDefinitionConversionListener +{ + + private static final String START_EVENT_ID = "start"; + + private static final String END_EVENT_ID = "end"; + + public void beforeStepsConversion(WorkflowDefinitionConversion conversion) + { + initializeProcess(conversion); + } + + protected void initializeProcess(WorkflowDefinitionConversion conversion) + { + WorkflowDefinition workflowDefinition = conversion.getWorkflowDefinition(); + + // Create new process + Process process = conversion.getProcess(); + process.setId(generateProcessId(workflowDefinition)); + process.setName(workflowDefinition.getName()); + process.setDocumentation(workflowDefinition.getDescription()); + + conversion.setProcess(process); + + // Add start-event + StartEvent startEvent = new StartEvent(); + startEvent.setId(START_EVENT_ID); + process.addFlowElement(startEvent); + conversion.setLastActivityId(startEvent.getId()); + } + + /** + * @param workflowDefinition + * @return process definition id that is randomized, to avoid name clashes (eg. amongst differnt tenants) + */ + protected String generateProcessId(WorkflowDefinition workflowDefinition) + { + return workflowDefinition.getName().replace(" ", "_"); + } + + public void afterStepsConversion(WorkflowDefinitionConversion conversion) + { + // Add end-event to process + Process process = conversion.getProcess(); + + EndEvent endEvent = new EndEvent(); + endEvent.setId(END_EVENT_ID); + process.addFlowElement(endEvent); + + // Sequence flow from last created activity to end + SequenceFlow sequenceFlow = new SequenceFlow(); + sequenceFlow.setId(conversion.getUniqueNumberedId(ConversionConstants.DEFAULT_SEQUENCEFLOW_PREFIX)); + sequenceFlow.setSourceRef(conversion.getLastActivityId()); + sequenceFlow.setTargetRef(END_EVENT_ID); + process.addFlowElement(sequenceFlow); + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/HumanStepDefinitionConverter.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/HumanStepDefinitionConverter.java new file mode 100644 index 0000000000..0353cc7a38 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/HumanStepDefinitionConverter.java @@ -0,0 +1,66 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + +import org.activiti.bpmn.model.UserTask; +import org.activiti.workflow.simple.definition.HumanStepDefinition; +import org.activiti.workflow.simple.definition.StepDefinition; + +/** + * @author Frederik Heremans + * @author Joram Barrez + */ +public class HumanStepDefinitionConverter extends BaseStepDefinitionConverter { + + private static final String USER_TASK_PREFIX = "userTask"; + + private static final String INITIATOR_ASSIGNEE_EXPRESSION = "${initiator.properties.userName}"; + + public Class< ? extends StepDefinition> getHandledClass() { + return HumanStepDefinition.class; + } + + protected UserTask createProcessArtifact(HumanStepDefinition stepDefinition, WorkflowDefinitionConversion conversion) { + UserTask userTask = createUserTask(stepDefinition, conversion); + addFlowElement(conversion, userTask); + + return userTask; + } + + protected UserTask createUserTask(HumanStepDefinition humanStepDefinition, WorkflowDefinitionConversion conversion) { + + // TODO: validate and throw exception on missing properties + + UserTask userTask = new UserTask(); + userTask.setId(conversion.getUniqueNumberedId(USER_TASK_PREFIX)); + userTask.setName(humanStepDefinition.getName()); + userTask.setDocumentation(humanStepDefinition.getDescription()); + + if (humanStepDefinition.isAssigneeInitiator()) { + userTask.setAssignee(INITIATOR_ASSIGNEE_EXPRESSION); + } else if (humanStepDefinition.getAssignee() != null) { + userTask.setAssignee(humanStepDefinition.getAssignee()); + } + + if (humanStepDefinition.getCandidateUsers() != null && humanStepDefinition.getCandidateUsers().size() > 0) { + userTask.setCandidateUsers(humanStepDefinition.getCandidateUsers()); + } + + if (humanStepDefinition.getCandidateGroups() != null && humanStepDefinition.getCandidateGroups().size() > 0) { + userTask.setCandidateGroups(humanStepDefinition.getCandidateGroups()); + } + + return userTask; + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/StepDefinitionConverter.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/StepDefinitionConverter.java new file mode 100644 index 0000000000..6855c4755d --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/StepDefinitionConverter.java @@ -0,0 +1,49 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + +import org.activiti.workflow.simple.definition.StepDefinition; + +/** + *

+ * A class that is responsible for converting a single {@link StepDefinition} to + * all required artifacts needed. This includes process-elements, content-types + * and forms. + *

+ * + *

+ * Please note that {@link StepDefinitionConverter} instances are reused and + * should be state-less. + *

+ * + * @author Frederik Heremans + */ +public interface StepDefinitionConverter { + + /** + * @return class that this converter is capable of handling. + */ + Class< ? extends StepDefinition> getHandledClass(); + + /** + * Convert given {@link StepDefinition} to correct artifacts and adds them to + * process, models and forms. + * + * @param stepDefinition + * the {@link StepDefinition} + * @param conversion + * context to add artifacts to + */ + void convertStepDefinition(StepDefinition stepDefinition, WorkflowDefinitionConversion conversion); + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversion.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversion.java new file mode 100644 index 0000000000..4f19138712 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversion.java @@ -0,0 +1,175 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.activiti.bpmn.converter.BpmnXMLConverter; +import org.activiti.bpmn.model.BpmnModel; +import org.activiti.bpmn.model.Process; +import org.activiti.engine.ActivitiException; +import org.activiti.workflow.simple.definition.StepDefinition; +import org.activiti.workflow.simple.definition.WorkflowDefinition; +import org.activiti.workflow.simple.diagram.WorkflowDIGenerator; + +/** + * Context that holds all artifacts and meta-data required when converting + * {@link WorkflowDefinition}s into required artifacts for deployment. + * + * @see StepDefinitionConverter + * + * @author Frederik Heremans + * @author Joram Barrez + */ +public class WorkflowDefinitionConversion { + + // Input + protected WorkflowDefinition workflowDefinition; + + // Artifacts of the conversion + protected BpmnModel bpmnModel; + protected Process process; + protected Map additionalArtifacts; + + // Helper members + protected WorkflowDefinitionConversionFactory conversionFactory; + protected String lastActivityId; + protected HashMap incrementalIdMapping; + + /* package */WorkflowDefinitionConversion(WorkflowDefinitionConversionFactory factory) { + this.conversionFactory = factory; + } + + /* package */ + public WorkflowDefinitionConversion(WorkflowDefinitionConversionFactory factory, WorkflowDefinition workflowDefinition) { + this(factory); + this.workflowDefinition = workflowDefinition; + } + + public void convert() { + + if (workflowDefinition == null) { + throw new ActivitiException("Cannot start conversion: need to set a WorkflowDefinition first!"); + } + + this.incrementalIdMapping = new HashMap(); + this.additionalArtifacts = new HashMap(); + + // Create new process + bpmnModel = new BpmnModel(); + process = new Process(); + bpmnModel.addProcess(process); + + // Let conversion listeners know initialization is finished + for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getWorkflowDefinitionConversionListeners()) { + conversionListener.beforeStepsConversion(this); + } + + // Convert each step + for (StepDefinition step : workflowDefinition.getSteps()) { + conversionFactory.getStepConverterFor(step).convertStepDefinition(step, this); + } + + // Let conversion listeners know step conversion is done + for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getWorkflowDefinitionConversionListeners()) { + conversionListener.afterStepsConversion(this); + } + + // Add DI information to bpmn model + WorkflowDIGenerator workflowDIGenerator = new WorkflowDIGenerator(workflowDefinition, bpmnModel); + workflowDIGenerator.generateDI(); + } + + /** + * @param baseName + * base name of the unique identifier + * @return a string that can be used as a unique id. Eg. if a baseName with + * value "userTask" is passed, the first time "userTask1" will be + * returned. When called agian with the same baseName, "userTask2" is + * returned. Counts are incremented for each baseName independently + * withing this context instance. + */ + public String getUniqueNumberedId(String baseName) { + Integer index = incrementalIdMapping.get(baseName); + if (index == null) { + index = 1; + incrementalIdMapping.put(baseName, index); + } else { + index = index + 1; + incrementalIdMapping.put(baseName, index); + } + return baseName + index; + } + + /** + * @return id of the activity that is at the end of the current process. Used + * to add additional steps and sequence-flows to the process. + */ + public String getLastActivityId() { + return this.lastActivityId; + } + + /** + * @param lastActivityId + * id of the activity that is at the end of the current process. Used + * to add additional steps and sequence-flows to the process. + */ + public void setLastActivityId(String lastActivityId) { + this.lastActivityId = lastActivityId; + } + + public BpmnModel getBpmnModel() { + return bpmnModel; + } + + public void setBpmnModel(BpmnModel bpmnModel) { + this.bpmnModel = bpmnModel; + } + + public Process getProcess() { + return process; + } + + public void setProcess(Process process) { + this.process = process; + } + + public Object getArtifact(String artifactKey) { + return additionalArtifacts.get(artifactKey); + } + + public void setArtifact(String artifactKey, Object artifact) { + additionalArtifacts.put(artifactKey, artifact); + } + + public WorkflowDefinition getWorkflowDefinition() { + return workflowDefinition; + } + + public void setWorkflowDefinition(WorkflowDefinition workflowDefinition) { + this.workflowDefinition = workflowDefinition; + } + + public String getbpm20Xml() { + BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter(); + return new String(bpmnXMLConverter.convertToXML(bpmnModel)); + } + + public InputStream getWorkflowDiagramImage() { + WorkflowDIGenerator workflowDIGenerator = new WorkflowDIGenerator(workflowDefinition, bpmnModel); + return workflowDIGenerator.generateDiagram(); + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionFactory.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionFactory.java new file mode 100644 index 0000000000..4f34799b46 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionFactory.java @@ -0,0 +1,79 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + +import java.util.HashMap; +import java.util.List; + +import org.activiti.workflow.simple.definition.StepDefinition; +import org.activiti.workflow.simple.definition.WorkflowDefinition; + +/** + * Factory that is capable of creating {@link WorkflowDefinitionConversion} + * objects. + * + * @author Frederik Heremans + * @author Joram Barrez + */ +public class WorkflowDefinitionConversionFactory { + + protected HashMap, StepDefinitionConverter> stepConverters; + protected List workflowDefinitionConversionListeners; + + /** + * @return a new, empty conversion to be used to store all converted + * artifacts. + */ + public WorkflowDefinitionConversion createWorkflowDefinitionConversion() { + return new WorkflowDefinitionConversion(this); + } + + public WorkflowDefinitionConversion createWorkflowDefinitionConversion(WorkflowDefinition workflowDefinition) { + return new WorkflowDefinitionConversion(this, workflowDefinition); + } + + /** + * @param stepConverters + * converter to register with this factory + */ + public void setStepDefinitionConverters(List stepConverters) { + this.stepConverters = new HashMap, StepDefinitionConverter>(); + for (StepDefinitionConverter converter : stepConverters) { + this.stepConverters.put(converter.getHandledClass(), converter); + } + } + + public List getWorkflowDefinitionConversionListeners() { + return workflowDefinitionConversionListeners; + } + + public void setWorkflowDefinitionConversionListeners(List workflowDefinitionConversionListeners) { + this.workflowDefinitionConversionListeners = workflowDefinitionConversionListeners; + } + + /** + * @param definition + * step definition to get converter for. + * @return Converter that can be used on the given definition. + * @throws IllegalArgumentException + * when there is no converter known for the given definition. + */ + public StepDefinitionConverter getStepConverterFor(StepDefinition definition) { + final StepDefinitionConverter converter = stepConverters.get(definition.getClass()); + if (converter == null) { + // TODO: i18n and error-handling + throw new IllegalArgumentException("No converter found for step: " + definition.getClass()); + } + return converter; + } +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionListener.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionListener.java new file mode 100644 index 0000000000..663772d07e --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/converter/WorkflowDefinitionConversionListener.java @@ -0,0 +1,24 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.converter; + +/** + * @author Joram Barrez + */ +public interface WorkflowDefinitionConversionListener { + + void beforeStepsConversion(WorkflowDefinitionConversion conversion); + + void afterStepsConversion(WorkflowDefinitionConversion conversion); + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormDefinition.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormDefinition.java new file mode 100644 index 0000000000..39b70fdee2 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormDefinition.java @@ -0,0 +1,47 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.definition; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Joram Barrez + */ +public class FormDefinition { + + protected String formKey; + + protected List formProperties = new ArrayList(); + + public List getFormProperties() { + return formProperties; + } + + public void setFormProperties(List formProperties) { + this.formProperties = formProperties; + } + + public void addFormProperty(FormPropertyDefinition formProperty) { + formProperties.add(formProperty); + } + + public String getFormKey() { + return formKey; + } + + public void setFormKey(String formKey) { + this.formKey = formKey; + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormPropertyDefinition.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormPropertyDefinition.java new file mode 100644 index 0000000000..29585f44a8 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/FormPropertyDefinition.java @@ -0,0 +1,50 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.definition; + +/** + * @author Joram Barrez + */ +public class FormPropertyDefinition { + + protected String propertyName; + + protected String type; + + protected boolean required; + + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/HumanStepDefinition.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/HumanStepDefinition.java new file mode 100644 index 0000000000..08dba860e1 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/HumanStepDefinition.java @@ -0,0 +1,71 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.definition; + +import java.util.List; + +/** + * @author Joram Barrez + */ +public class HumanStepDefinition extends StepDefinition { + + protected String assignee; + + protected boolean isAssigneeInitiator = false; + + protected List candidateUsers; + + protected List candidateGroups; + + protected FormDefinition form; + + public boolean isAssigneeInitiator() { + return isAssigneeInitiator; + } + + public void setAssigneeInitiator(boolean isAssigneeInitiator) { + this.isAssigneeInitiator = isAssigneeInitiator; + } + + public String getAssignee() { + return assignee; + } + + public void setAssignee(String assignee) { + this.assignee = assignee; + } + + public List getCandidateUsers() { + return candidateUsers; + } + + public void setCandidateUsers(List candidateUsers) { + this.candidateUsers = candidateUsers; + } + + public List getCandidateGroups() { + return candidateGroups; + } + + public void setCandidateGroups(List candidateGroups) { + this.candidateGroups = candidateGroups; + } + + public FormDefinition getForm() { + return form; + } + + public void setForm(FormDefinition form) { + this.form = form; + } +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinition.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinition.java new file mode 100644 index 0000000000..276a4b8405 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinition.java @@ -0,0 +1,48 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.definition; + +/** + * @author Joram Barrez + */ +public class StepDefinition { + + protected String name; + protected String description; + boolean isStartWithPrevious; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isStartWithPrevious() { + return isStartWithPrevious; + } + + public void setStartWithPrevious(boolean isStartWithPrevious) { + this.isStartWithPrevious = isStartWithPrevious; + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinitionContainer.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinitionContainer.java new file mode 100644 index 0000000000..a48e566195 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/StepDefinitionContainer.java @@ -0,0 +1,27 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.definition; + + +/** + * @author Joram Barrez + */ +public interface StepDefinitionContainer { + + void addStep(StepDefinition stepDefinition); + + StepDefinitionContainer addHumanStep(String name, String assignee); + + StepDefinitionContainer addHumanStepForWorkflowInitiator(String name); + +} \ No newline at end of file diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/WorkflowDefinition.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/WorkflowDefinition.java new file mode 100644 index 0000000000..bd24e6f507 --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/definition/WorkflowDefinition.java @@ -0,0 +1,131 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.definition; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Joram Barrez + */ +public class WorkflowDefinition implements StepDefinitionContainer { + + protected String name; + protected String description; + protected List steps = new ArrayList(); + protected ParallelBlock currentParallelBlock; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public WorkflowDefinition name(String name) { + setName(name); + return this; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public WorkflowDefinition description(String description) { + setDescription(description); + return this; + } + + public void addStep(StepDefinition stepDefinition) { + steps.add(stepDefinition); + } + + public List getSteps() { + return steps; + } + + public WorkflowDefinition addHumanStep(String name, String assignee) { + return addHumanStep(name, assignee, false); + } + + public WorkflowDefinition addHumanStepForWorkflowInitiator(String name) { + return addHumanStep(name, null, true); + } + + protected WorkflowDefinition addHumanStep(String name, String assignee, boolean initiator) { + HumanStepDefinition humanStepDefinition = new HumanStepDefinition(); + + if (name != null) { + humanStepDefinition.setName(name); + } + + if (assignee != null) { + humanStepDefinition.setAssignee(assignee); + } + + humanStepDefinition.setAssigneeInitiator(initiator); + humanStepDefinition.setStartWithPrevious(currentParallelBlock != null); + + addStep(humanStepDefinition); + return this; + } + + public ParallelBlock inParallel() { + currentParallelBlock = new ParallelBlock(this); + return currentParallelBlock; + } + + public WorkflowDefinition endParallel() { + currentParallelBlock = null; + return this; + } + + // Helper classes + + public static class ParallelBlock implements StepDefinitionContainer { + + protected WorkflowDefinition workflowDefinition; + + public ParallelBlock(WorkflowDefinition workflowDefinition) { + this.workflowDefinition = workflowDefinition; + } + + public void addStep(StepDefinition stepDefinition) { + workflowDefinition.addStep(stepDefinition); + } + + public ParallelBlock addHumanStep(String name, String assignee) { + workflowDefinition.addHumanStep(name, assignee); + return this; + } + + public ParallelBlock addHumanStepForWorkflowInitiator(String name) { + workflowDefinition.addHumanStepForWorkflowInitiator(name); + return this; + } + + public WorkflowDefinition endParallel() { + workflowDefinition.endParallel(); + return workflowDefinition; + } + + + } + +} diff --git a/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/diagram/WorkflowDIGenerator.java b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/diagram/WorkflowDIGenerator.java new file mode 100644 index 0000000000..74c87d902e --- /dev/null +++ b/modules/activiti-simple-workflow/src/main/java/org/activiti/workflow/simple/diagram/WorkflowDIGenerator.java @@ -0,0 +1,451 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple.diagram; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.activiti.bpmn.model.BpmnModel; +import org.activiti.bpmn.model.EndEvent; +import org.activiti.bpmn.model.FlowElement; +import org.activiti.bpmn.model.GraphicInfo; +import org.activiti.bpmn.model.ParallelGateway; +import org.activiti.bpmn.model.Process; +import org.activiti.bpmn.model.ScriptTask; +import org.activiti.bpmn.model.SequenceFlow; +import org.activiti.bpmn.model.ServiceTask; +import org.activiti.bpmn.model.StartEvent; +import org.activiti.bpmn.model.Task; +import org.activiti.bpmn.model.UserTask; +import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramCanvas; +import org.activiti.workflow.simple.definition.StepDefinition; +import org.activiti.workflow.simple.definition.WorkflowDefinition; + +/** + * @author Joram Barrez + */ +public class WorkflowDIGenerator { + + // Constants + protected static final int SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH = 45; + protected static final int ARROW_WIDTH = 5; + protected static final int SEQUENCE_FLOW_WIDTH = SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH + ARROW_WIDTH; + + protected static final int LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH = SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH * 2; + protected static final int LONG_SEQUENCE_FLOW_WIDTH = LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH + ARROW_WIDTH; + + protected static final int TASK_WIDTH = 130; + protected static final int TASK_HEIGHT = 60; + protected static final int TASK_HEIGHT_SPACING = 10; + + protected static final int EVENT_WIDTH = 20; + + protected static final int GATEWAY_WIDTH = 40; + protected static final int GATEWAY_HEIGHT = 40; + + protected int TASK_BLOCK_WIDTH = GATEWAY_WIDTH + TASK_WIDTH + SEQUENCE_FLOW_WIDTH + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH; + + // Input + protected WorkflowDefinition workflowDefinition; + + protected BpmnModel bpmnModel; + protected Process process; + + // Will be set during image generation + protected int startX; + protected int startY; + protected int currentWidth; + protected ProcessDiagramCanvas processDiagramCanvas; + protected List allStepBlocks; + protected Map> outgoingSequenceFlowMapping; + protected Map> incomingSequenceFlowMapping; + protected Set handledElements; + + public WorkflowDIGenerator(WorkflowDefinition workflowDefinition, BpmnModel bpmnModel) { + this.workflowDefinition = workflowDefinition; + this.bpmnModel = bpmnModel; + } + + public void generateDI() { + generateDI(false); + } + + protected void generateDI(boolean generateImage) { + + // Reset any previous DI information + bpmnModel.getLocationMap().clear(); + bpmnModel.getFlowLocationMap().clear(); + bpmnModel.getLocationMap().clear(); + + process = bpmnModel.getProcesses().get(0); // will always contain just one + + // Create task blocks (used for image generation and canvas size calculation) + generateTaskBlocks(); + + // Calulcate image height and width + this.startX = 0; + this.startY = calculateMaximumHeight() / 2 + 10; + this.currentWidth = 0; + + if (generateImage) { + int width = calculateMaximumWidth() + 50; + int height = calculateMaximumHeight() + 50; + processDiagramCanvas = new ProcessDiagramCanvas(width, height); + } + + Collection flowElements = process.getFlowElements(); + generateSequenceflowMappings(flowElements); + this.handledElements = new HashSet(); + + // Enough preparation, actually draw some stuff + for (FlowElement flowElement : flowElements) { + + if (!handledElements.contains(flowElement.getId())) { + + if (flowElement instanceof StartEvent) { + + drawStartEvent(flowElement, startX, startY, EVENT_WIDTH, EVENT_WIDTH, generateImage); + + } else if (flowElement instanceof EndEvent) { + + drawSequenceFlow(incomingSequenceFlowMapping.get(flowElement.getId()).get(0), + generateImage, + currentWidth, startY + EVENT_WIDTH / 2, currentWidth + + SEQUENCE_FLOW_WIDTH, startY + EVENT_WIDTH / 2); + drawEndEvent(flowElement, currentWidth, startY, EVENT_WIDTH, EVENT_WIDTH, generateImage); + + } else if (flowElement instanceof ParallelGateway + && outgoingSequenceFlowMapping.get(flowElement.getId()).size() > 1) { // fork + + ParallelGateway parallelGateway = (ParallelGateway) flowElement; + drawSequenceFlow(incomingSequenceFlowMapping.get(flowElement.getId()).get(0), + generateImage, + currentWidth, startY + EVENT_WIDTH / 2, currentWidth + + SEQUENCE_FLOW_WIDTH, startY + EVENT_WIDTH / 2); + drawParallelBlock(currentWidth, startY - EVENT_WIDTH / 2, parallelGateway, generateImage); + + } else if (flowElement instanceof Task) { + drawSequenceFlow(incomingSequenceFlowMapping.get(flowElement.getId()).get(0), + generateImage, + currentWidth, startY + EVENT_WIDTH / 2, currentWidth + + SEQUENCE_FLOW_WIDTH, startY + EVENT_WIDTH / 2); + drawTask(flowElement, currentWidth, startY - ((TASK_HEIGHT - EVENT_WIDTH) / 2), + TASK_WIDTH, TASK_HEIGHT, generateImage); + } + } + } + } + + public InputStream generateDiagram() { + generateDI(true); // Generates DI and also the canvas which can export the image + return processDiagramCanvas.generateImage("png"); + } + + protected void generateTaskBlocks() { + allStepBlocks = new ArrayList(); + + List workflowSteps = workflowDefinition.getSteps(); + for (int i=0; i flowElements) { + this.outgoingSequenceFlowMapping = new HashMap>(); + this.incomingSequenceFlowMapping = new HashMap>(); + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof SequenceFlow) { + SequenceFlow sequenceFlow = (SequenceFlow) flowElement; + String srcId = sequenceFlow.getSourceRef(); + String targetId = sequenceFlow.getTargetRef(); + + if (outgoingSequenceFlowMapping.get(srcId) == null) { + outgoingSequenceFlowMapping.put(srcId, new ArrayList()); + } + outgoingSequenceFlowMapping.get(srcId).add(sequenceFlow); + + if (incomingSequenceFlowMapping.get(targetId) == null) { + incomingSequenceFlowMapping.put(targetId, new ArrayList()); + } + incomingSequenceFlowMapping.get(targetId).add(sequenceFlow); + } + } + } + + protected int calculateMaximumWidth() { + int width = 0; + for (BlockOfSteps blockOfSteps : allStepBlocks) { + if (blockOfSteps.getNrOfSteps() == 1) { + width += TASK_WIDTH + SEQUENCE_FLOW_WIDTH; + } else { + width += TASK_BLOCK_WIDTH + SEQUENCE_FLOW_WIDTH; + } + } + + width += SEQUENCE_FLOW_WIDTH + 2 * EVENT_WIDTH; + + return width; + } + + protected int calculateMaximumHeight() { + int maxNrOfTasksInOneBlock = 0; + for (BlockOfSteps blockOfSteps : allStepBlocks) { + if (blockOfSteps.getNrOfSteps() > maxNrOfTasksInOneBlock) { + maxNrOfTasksInOneBlock = blockOfSteps.getNrOfSteps(); + } + } + + int extra = 0; + if (maxNrOfTasksInOneBlock % 2 == 0) { // If there is an even nr of tasks -> evenly spread, but no task in the middle + extra = 2 * TASK_HEIGHT; + } + + return (maxNrOfTasksInOneBlock * (TASK_HEIGHT + TASK_HEIGHT_SPACING)) + extra; + } + + protected void drawParallelBlock(int x, int y, ParallelGateway parallelGateway, boolean generateImage) { + + int originalCurrentWidth = currentWidth; + List sequenceFlows = outgoingSequenceFlowMapping.get(parallelGateway.getId()); + int nrOfTasks = sequenceFlows.size(); + + // First parallel gateway + drawParallelGateway(parallelGateway, x, y, GATEWAY_WIDTH, GATEWAY_HEIGHT, generateImage); + handledElements.add(parallelGateway.getId()); + + // Sequence flow up and down + int centerOfRhombus = x + GATEWAY_WIDTH / 2; + int maxHeight = (nrOfTasks / 2) * (TASK_HEIGHT + TASK_HEIGHT_SPACING); + + int currentHeight = y - maxHeight; + + // first half + for (int i = 0; i < nrOfTasks / 2; i++) { + SequenceFlow sequenceFlow1 = sequenceFlows.get(i); + drawSequenceFlow(sequenceFlow1, generateImage, centerOfRhombus, y, centerOfRhombus, currentHeight, + centerOfRhombus + SEQUENCE_FLOW_WIDTH, currentHeight); + + String targetFlowElementId = sequenceFlow1.getTargetRef(); + FlowElement userTask = process.getFlowElement(targetFlowElementId); + drawTask(userTask, centerOfRhombus + SEQUENCE_FLOW_WIDTH, + currentHeight - ((TASK_HEIGHT + TASK_HEIGHT_SPACING) / 2), TASK_WIDTH, TASK_HEIGHT, generateImage); + handledElements.add(userTask.getId()); + + int seqFlowX = centerOfRhombus + SEQUENCE_FLOW_WIDTH + TASK_WIDTH; + SequenceFlow sequenceFlow2 = outgoingSequenceFlowMapping.get(userTask.getId()).get(0); + drawSequenceFlow(sequenceFlow2, generateImage, + seqFlowX, currentHeight, + seqFlowX + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, currentHeight, + seqFlowX + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, y); + + currentHeight += TASK_HEIGHT + TASK_HEIGHT_SPACING; + } + + // middle task + if (nrOfTasks % 2 != 0) { + SequenceFlow sequenceFlow1 = sequenceFlows.get(nrOfTasks / 2); + drawSequenceFlow(sequenceFlow1, generateImage, + centerOfRhombus + GATEWAY_WIDTH / 2, + startY + EVENT_WIDTH / 2, centerOfRhombus + SEQUENCE_FLOW_WIDTH, + startY + EVENT_WIDTH / 2); + + String targetFlowElementId = sequenceFlow1.getTargetRef(); + FlowElement userTask = process.getFlowElement(targetFlowElementId); + drawTask(userTask, centerOfRhombus + SEQUENCE_FLOW_WIDTH, + startY - ((TASK_HEIGHT - GATEWAY_HEIGHT)), TASK_WIDTH, TASK_HEIGHT, generateImage); + handledElements.add(userTask.getId()); + + int seqflowX = centerOfRhombus + GATEWAY_WIDTH / 2 + (SEQUENCE_FLOW_WIDTH - GATEWAY_WIDTH / 2) + TASK_WIDTH; + SequenceFlow sequenceFlow2 = outgoingSequenceFlowMapping.get(userTask.getId()).get(0); + drawSequenceFlow(sequenceFlow2, generateImage, + seqflowX, startY + EVENT_WIDTH / 2, + seqflowX + LONG_SEQUENCE_FLOW_WIDTH - GATEWAY_WIDTH / 2 - ARROW_WIDTH, startY + + EVENT_WIDTH / 2); + } + + currentHeight = y + GATEWAY_HEIGHT + TASK_HEIGHT + TASK_HEIGHT_SPACING; + + // second half + int startIndex = nrOfTasks % 2 == 0 ? nrOfTasks / 2 : (nrOfTasks / 2) + 1; + for (int i = startIndex; i < nrOfTasks; i++) { + SequenceFlow sequenceFlow1 = sequenceFlows.get(i); + drawSequenceFlow(sequenceFlow1, generateImage, + centerOfRhombus, y + GATEWAY_HEIGHT, centerOfRhombus, + currentHeight, centerOfRhombus + SEQUENCE_FLOW_WIDTH, currentHeight); + + String targetFlowElementId = sequenceFlow1.getTargetRef(); + FlowElement userTask = process.getFlowElement(targetFlowElementId); + drawTask(userTask, centerOfRhombus + SEQUENCE_FLOW_WIDTH, + currentHeight - ((TASK_HEIGHT + TASK_HEIGHT_SPACING) / 2), TASK_WIDTH, + TASK_HEIGHT, generateImage); + + int seqFlowX = centerOfRhombus + SEQUENCE_FLOW_WIDTH + TASK_WIDTH; + SequenceFlow sequenceFlow2 = outgoingSequenceFlowMapping.get(userTask.getId()).get(0); + drawSequenceFlow(sequenceFlow2, generateImage, + seqFlowX, currentHeight, + seqFlowX + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, currentHeight, seqFlowX + + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, y + GATEWAY_HEIGHT); + handledElements.add(userTask.getId()); + + currentHeight += TASK_HEIGHT + TASK_HEIGHT_SPACING; + } + + // Second parallel gateway + String someTaskId = sequenceFlows.get(0).getTargetRef(); + FlowElement join = process.getFlowElement(outgoingSequenceFlowMapping.get(someTaskId).get(0).getTargetRef()); + centerOfRhombus = centerOfRhombus + SEQUENCE_FLOW_WIDTH + TASK_WIDTH + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH; + drawParallelGateway(join, centerOfRhombus - GATEWAY_WIDTH / 2, y, GATEWAY_WIDTH, GATEWAY_HEIGHT, generateImage); + handledElements.add(join.getId()); + + currentWidth = originalCurrentWidth + TASK_BLOCK_WIDTH; + } + + protected void drawStartEvent(FlowElement flowElement, int x, int y, int width, int height, boolean generateImage) { + if (generateImage) { + processDiagramCanvas.drawNoneStartEvent(x, y, width, height); + } + + createDiagramInterchangeInformation(flowElement, x, y, width, height); + currentWidth += EVENT_WIDTH; + } + + protected void drawEndEvent(FlowElement flowElement, int x, int y, int width, int height, boolean generateImage) { + if (generateImage) { + processDiagramCanvas.drawNoneEndEvent(x, y, width, height); + } + + createDiagramInterchangeInformation(flowElement, x, y, width, height); + currentWidth += EVENT_WIDTH; + } + + protected void drawParallelGateway(FlowElement flowElement, int x, int y, int width, int height, boolean generateImage) { + if (generateImage) { + processDiagramCanvas.drawParallelGateway(x, y, width, height); + } + + createDiagramInterchangeInformation(flowElement, x, y, width, height); + currentWidth += GATEWAY_WIDTH; + } + + protected void drawTask(FlowElement flowElement, int x, int y, int width, int height, boolean generateImage) { + if (generateImage) { + if (flowElement instanceof UserTask) { + processDiagramCanvas.drawUserTask(flowElement.getName(), x, y, width, height); + } else if (flowElement instanceof ServiceTask) { + processDiagramCanvas.drawServiceTask(flowElement.getName(), x, y, width, height); + } else if (flowElement instanceof ScriptTask) { + processDiagramCanvas.drawScriptTask(flowElement.getName(), x, y, width, height); + } + } + + createDiagramInterchangeInformation(flowElement, x, y, width, height); + currentWidth += TASK_WIDTH; + } + + protected void drawSequenceFlow(SequenceFlow sequenceFlow, boolean generateImage, int... waypoints) { + + // Draw on diagram canvas + + int minX = Integer.MAX_VALUE; + int maxX = 0; + for (int i = 2; i < waypoints.length; i += 2) { // waypoints.size() + + if (generateImage) { + // minimally 4: x1, y1, x2, y2 + if (i < waypoints.length - 2) { + processDiagramCanvas.drawSequenceflowWithoutArrow(waypoints[i - 2], + waypoints[i - 1], waypoints[i], waypoints[i + 1], false); + } else { + processDiagramCanvas.drawSequenceflow(waypoints[i - 2], + waypoints[i - 1], waypoints[i], waypoints[i + 1], false); + } + } + + if (waypoints[i - 2] < minX || waypoints[i] < minX) { + minX = Math.min(waypoints[i - 2], waypoints[i]); + } + if (waypoints[i - 2] > maxX || waypoints[i] > maxX) { + maxX = Math.max(waypoints[i - 2], waypoints[i]); + } + } + + currentWidth += maxX - minX; + + // Generate DI information + List graphicInfoForWaypoints = new ArrayList(); + for (int i = 0; i < waypoints.length; i += 2) { + GraphicInfo graphicInfo = new GraphicInfo(); + graphicInfo.element = sequenceFlow; + graphicInfo.x = waypoints[i]; + graphicInfo.y = waypoints[i + 1]; + graphicInfoForWaypoints.add(graphicInfo); + } + bpmnModel.addFlowGraphicInfoList(sequenceFlow.getId(), graphicInfoForWaypoints); + + } + + protected void createDiagramInterchangeInformation(FlowElement flowElement, + int x, int y, int width, int height) { + GraphicInfo graphicInfo = new GraphicInfo(); + graphicInfo.x = x; + graphicInfo.y = y; + graphicInfo.width = width; + graphicInfo.height = height; + graphicInfo.element = flowElement; + bpmnModel.addGraphicInfo(flowElement.getId(), graphicInfo); + } + + // Helper class ------------------------------------------------------------------------ + + static class BlockOfSteps { + + protected List steps; + + public BlockOfSteps() { + this.steps = new ArrayList(); + } + + public List getSteps() { + return steps; + } + + public void addStep(StepDefinition step) { + steps.add(step); + } + + public StepDefinition get(int index) { + return steps.get(index); + } + + public int getNrOfSteps() { + return steps.size(); + } + + } + +} diff --git a/modules/activiti-simple-workflow/src/test/java/org/activiti/workflow/simple/WorkflowConversionTest.java b/modules/activiti-simple-workflow/src/test/java/org/activiti/workflow/simple/WorkflowConversionTest.java new file mode 100644 index 0000000000..a415445565 --- /dev/null +++ b/modules/activiti-simple-workflow/src/test/java/org/activiti/workflow/simple/WorkflowConversionTest.java @@ -0,0 +1,182 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.activiti.workflow.simple; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.activiti.engine.repository.Deployment; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.ProcessInstance; +import org.activiti.engine.task.Task; +import org.activiti.engine.test.ActivitiRule; +import org.activiti.workflow.simple.converter.DefaultWorkflowDefinitionConversionListener; +import org.activiti.workflow.simple.converter.HumanStepDefinitionConverter; +import org.activiti.workflow.simple.converter.StepDefinitionConverter; +import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion; +import org.activiti.workflow.simple.converter.WorkflowDefinitionConversionFactory; +import org.activiti.workflow.simple.converter.WorkflowDefinitionConversionListener; +import org.activiti.workflow.simple.definition.WorkflowDefinition; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Joram Barrez + */ +public class WorkflowConversionTest { + + private static Logger log = LoggerFactory.getLogger(WorkflowConversionTest.class); + + @Rule + public ActivitiRule activitiRule = new ActivitiRule(); + + protected WorkflowDefinitionConversionFactory conversionFactory; + + @Before + public void initialiseTest() { + + // Alternatively, the following setup could be done using a dependency injection container + + conversionFactory = new WorkflowDefinitionConversionFactory(); + + // Steps + List stepConverters = new ArrayList(); + stepConverters.add(new HumanStepDefinitionConverter()); + conversionFactory.setStepDefinitionConverters(stepConverters); + + // Listeners + List conversionListeners = new ArrayList(); + conversionListeners.add(new DefaultWorkflowDefinitionConversionListener()); + conversionFactory.setWorkflowDefinitionConversionListeners(conversionListeners); + } + + @After + public void cleanup() { + for (Deployment deployment : activitiRule.getRepositoryService().createDeploymentQuery().list()) { + activitiRule.getRepositoryService().deleteDeployment(deployment.getId(), true); + } + } + + @Test + public void testSimplestProcess() { + WorkflowDefinition workflowDefinition = new WorkflowDefinition() + .name("testWorkflow") + .description("This is a test workflow"); + + // Validate + ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey(convertAndDeploy(workflowDefinition)); + assertTrue(processInstance.isEnded()); + } + + @Test + public void testUserTasksWithOnlyAssignees() { + String[] assignees = new String[] {"kermit", "gonzo", "mispiggy"}; + + WorkflowDefinition workflowDefinition = new WorkflowDefinition() + .name("testWorkflow") + .description("This is a test workflow") + .addHumanStep("first task", assignees[0]) + .addHumanStep("second step", assignees[1]) + .addHumanStep("third step", assignees[2]); + + // Validate + activitiRule.getRuntimeService().startProcessInstanceByKey(convertAndDeploy(workflowDefinition)); + for (String assignee : assignees) { + Task task = activitiRule.getTaskService().createTaskQuery().singleResult(); + assertEquals(assignee, task.getAssignee()); + activitiRule.getTaskService().complete(task.getId()); + } + + assertEquals(0, activitiRule.getRuntimeService().createProcessInstanceQuery().count()); + } + +// @Test +// public void testTwoUserTasksInParallel() { +// WorkflowDefinition workflowDefinition = new WorkflowDefinition() +// .name("testWorkflow") +// .description("This is a test workflow") +// .inParallel() +// .addHumanStep("first task", "kermit") +// .addHumanStep("second step", "gonzo") +// .endParallel(); +// +// // Validate +// activitiRule.getRuntimeService().startProcessInstanceByKey(convertAndDeploy(workflowDefinition)); +// assertEquals(1, activitiRule.getTaskService().createTaskQuery().taskAssignee("kermit").count()); +// assertEquals(1, activitiRule.getTaskService().createTaskQuery().taskAssignee("gonzo").count()); +// } + + // Helper methods ----------------------------------------------------------------------------- + + protected String convertAndDeploy(WorkflowDefinition workflowDefinition) { + + // Convert + WorkflowDefinitionConversion conversion = conversionFactory.createWorkflowDefinitionConversion(workflowDefinition); + conversion.convert(); + log.info("Converted process : " + conversion.getbpm20Xml()); + +// InputStream is = conversion.getWorkflowDiagramImage(); +// try { +// flow(is, new FileOutputStream("temp.png"), new byte[1024]); +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } + + // Deploy + deployProcessDefinition(conversion); + return getDeployedProcessKey(); + } + public static void flow( InputStream is, OutputStream os, byte[] buf ) + throws IOException { + int numRead; + while ( (numRead = is.read(buf) ) >= 0) { + os.write(buf, 0, numRead); + } + } + + protected String getDeployedProcessKey() { + ProcessDefinition processDefinition = activitiRule.getRepositoryService().createProcessDefinitionQuery().singleResult(); + assertNotNull(processDefinition); + return processDefinition.getKey(); + } + + protected void deployProcessDefinition(WorkflowDefinitionConversion conversion) { + long nrOfDeployments = countNrOfDeployments(); + + activitiRule.getRepositoryService().createDeployment() + .addString(conversion.getProcess().getId() + ".bpmn20.xml", conversion.getbpm20Xml()) + .deploy(); + + assertEquals(nrOfDeployments + 1, countNrOfDeployments()); + } + + protected long countNrOfDeployments() { + return activitiRule.getRepositoryService().createDeploymentQuery().count(); + } + +} diff --git a/modules/activiti-simple-workflow/src/test/resources/activiti.cfg.xml b/modules/activiti-simple-workflow/src/test/resources/activiti.cfg.xml new file mode 100644 index 0000000000..ee71dd8c11 --- /dev/null +++ b/modules/activiti-simple-workflow/src/test/resources/activiti.cfg.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/activiti-simple-workflow/src/test/resources/log4j.properties b/modules/activiti-simple-workflow/src/test/resources/log4j.properties new file mode 100644 index 0000000000..4aca13e7b0 --- /dev/null +++ b/modules/activiti-simple-workflow/src/test/resources/log4j.properties @@ -0,0 +1,10 @@ +log4j.rootLogger=INFO, CA + +# ConsoleAppender +log4j.appender.CA=org.apache.log4j.ConsoleAppender +log4j.appender.CA.layout=org.apache.log4j.PatternLayout +log4j.appender.CA.layout.ConversionPattern= %d{hh:MM:ss,SSS} [%t] %-5p %c %x - %m%n + + +log4j.logger.org.apache.ibatis.level=INFO +log4j.logger.javax.activation.level=INFO diff --git a/pom.xml b/pom.xml index 86d2ed5f1d..739a3d11be 100644 --- a/pom.xml +++ b/pom.xml @@ -752,6 +752,7 @@ modules/activiti-bpmn-model modules/activiti-bpmn-converter modules/activiti-json-converter + modules/activiti-simple-workflow modules/activiti-modeler modules/activiti-explorer modules/activiti-rest diff --git a/userguide/src/en/chapters/ch06-Deployment.xml b/userguide/src/en/chapters/ch06-Deployment.xml index 038be22b77..0bdd0809da 100644 --- a/userguide/src/en/chapters/ch06-Deployment.xml +++ b/userguide/src/en/chapters/ch06-Deployment.xml @@ -251,7 +251,7 @@ repositoryService.createDeployment() If, for some reason, it is not necessary or wanted to generate a diagram during deployment the isCreateDiagramOnDeploy property can be set on the process engine configuration: - <property name="isCreateDiagramOnDeploy" value="false" /> + <property name="createDiagramOnDeploy" value="false" /> No diagram will be generated now. -- GitLab