提交 75a0ad7e 编写于 作者: T tijsrademakers

Merge branch 'master' of https://github.com/Activiti/Activiti

......@@ -129,6 +129,14 @@
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
<!-- Required for testing JTA -->
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
<version>2.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
......@@ -472,12 +480,6 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
<version>2.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
......
......@@ -128,6 +128,7 @@ public abstract class ProcessEngineConfiguration implements EngineServices {
protected String activityFontName = "Arial";
protected ClassLoader classLoader;
protected ProcessEngineLifecycleListener processEngineLifecycleListener;
/** use one of the static createXxxx methods instead */
protected ProcessEngineConfiguration() {
......@@ -510,6 +511,12 @@ public abstract class ProcessEngineConfiguration implements EngineServices {
public void setActivityFontName(String activityFontName) {
this.activityFontName = activityFontName;
}
public void setProcessEngineLifecycleListener(ProcessEngineLifecycleListener processEngineLifecycleListener) {
this.processEngineLifecycleListener = processEngineLifecycleListener;
}
public ProcessEngineLifecycleListener getProcessEngineLifecycleListener() {
return processEngineLifecycleListener;
}
}
/* 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.engine;
/**
* Interface describing a listener that get's notified when certain event occurs,
* related to the process-engine lifecycle it is attached to.
*
* @author Frederik Heremans
*/
public interface ProcessEngineLifecycleListener {
/**
* Called right after the process-engine has been built.
*
* @param processEngine engine that was built
*/
void onProcessEngineBuilt(ProcessEngine processEngine);
/**
* Called right after the process-engine has been closed.
*
* @param processEngine engine that was closed
*/
void onProcessEngineClosed(ProcessEngine processEngine);
}
......@@ -86,6 +86,11 @@ public class ProcessEngineImpl implements ProcessEngine {
if ((jobExecutor != null) && (jobExecutor.isAutoActivate())) {
jobExecutor.start();
}
if(processEngineConfiguration.getProcessEngineLifecycleListener() != null)
{
processEngineConfiguration.getProcessEngineLifecycleListener().onProcessEngineBuilt(this);
}
}
public void close() {
......@@ -95,6 +100,11 @@ public class ProcessEngineImpl implements ProcessEngine {
}
commandExecutor.execute(new SchemaOperationProcessEngineClose());
if(processEngineConfiguration.getProcessEngineLifecycleListener() != null)
{
processEngineConfiguration.getProcessEngineLifecycleListener().onProcessEngineClosed(this);
}
}
public DbSqlSessionFactory getDbSqlSessionFactory() {
......
......@@ -95,6 +95,9 @@
<if test="processInstanceId != null">
RES.PROC_INST_ID_ = #{processInstanceId}
</if>
<if test="taskId != null">
RES.TASK_ID_ = #{taskId}
</if>
<if test="variableName != null">
and RES.NAME_ = #{variableName}
</if>
......
......@@ -741,7 +741,10 @@ public class ExecutionQueryTest extends PluggableActivitiTestCase {
ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("oneTaskProcess", vars);
Date date2 = Calendar.getInstance().getTime();
Calendar cal2 = Calendar.getInstance();
cal2.add(Calendar.SECOND, 1);
Date date2 = cal2.getTime();
vars = new HashMap<String, Object>();
vars.put("dateVar", date1);
vars.put("dateVar2", date2);
......
......@@ -723,7 +723,10 @@ public class ProcessInstanceQueryTest extends PluggableActivitiTestCase {
ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("oneTaskProcess", vars);
Date date2 = Calendar.getInstance().getTime();
Calendar cal2 = Calendar.getInstance();
cal2.add(Calendar.SECOND, 1);
Date date2 = cal2.getTime();
vars = new HashMap<String, Object>();
vars.put("dateVar", date1);
vars.put("dateVar2", date2);
......
......@@ -13,11 +13,16 @@
package org.activiti.engine.test.history;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.history.HistoricVariableInstance;
......@@ -275,4 +280,38 @@ public class HistoricVariableInstanceTest extends AbstractActivitiTestCase {
*/
assertFalse(historicActivityInstance2.getExecutionId().equals(update2.getExecutionId()));
}
// Test for ACT-1528, which (correctly) reported that deleting any
// historic process instance would remove ALL historic variables.
// Yes. Real serious bug.
@Deployment
public void testHistoricProcessInstanceDeleteCascadesCorrectly() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("var1", "value1");
variables.put("var2", "value2");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess", variables);
assertNotNull(processInstance);
variables = new HashMap<String, Object>();
variables.put("var3", "value3");
variables.put("var4", "value4");
ProcessInstance processInstance2 = runtimeService.startProcessInstanceByKey("myProcess", variables);
assertNotNull(processInstance2);
// check variables
long count = historyService.createHistoricVariableInstanceQuery().count();
assertEquals(4, count);
// delete runtime execution of ONE process instance
runtimeService.deleteProcessInstance(processInstance.getId(), "reason 1");
historyService.deleteHistoricProcessInstance(processInstance.getId());
// recheck variables
// this is a bug: all variables was deleted after delete a history processinstance
count = historyService.createHistoricVariableInstanceQuery().count();
assertEquals(2, count);
}
}
/* 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.standalone.jta;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineLifecycleListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import bitronix.tm.BitronixTransactionManager;
import bitronix.tm.resource.jdbc.PoolingDataSource;
/**
* Used in JPA-tests to close the XA-datasource after engine is closed, due to internal caching
* of datasource, independant of process-enging/spring-context.
*
* @author Frederik Heremans
*/
public class CloseXADataSourceLifecycleListener implements ProcessEngineLifecycleListener {
private PoolingDataSource dataSource;
private BitronixTransactionManager transactionManager;
private static final Logger LOG = LoggerFactory.getLogger(CloseXADataSourceLifecycleListener.class);
@Override
public void onProcessEngineBuilt(ProcessEngine processEngine) {
LOG.info("--------------------- Callback for engine start");
}
@Override
public void onProcessEngineClosed(ProcessEngine processEngine) {
LOG.info("--------------------- Callback for engine end");
if(dataSource != null) {
LOG.info("--------------------- Closing datasource");
dataSource.close();
}
if(transactionManager != null) {
transactionManager.shutdown();
}
}
public void setDataSource(PoolingDataSource dataSource) {
this.dataSource = dataSource;
}
public void setTransactionManager(BitronixTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="myProcess" name="Process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="User Task"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_Process1">
<bpmndi:BPMNPlane bpmnElement="Process1" id="BPMNPlane_Process1">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="100.0" y="100.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="180.0" y="90.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="340.0" y="100.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="135.0" y="117.0"></omgdi:waypoint>
<omgdi:waypoint x="180.0" y="117.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="285.0" y="117.0"></omgdi:waypoint>
<omgdi:waypoint x="340.0" y="117.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
\ No newline at end of file
......@@ -126,7 +126,7 @@ public class EditorProcessDefinitionDetailPanel extends DetailPanel {
importModelButton.addListener(new ImportModelClickListener());
editModelButton = new Button(i18nManager.getMessage(Messages.PROCESS_EDIT));
editModelButton.addListener(new EditModelClickListener(modelData.getId()));
editModelButton.addListener(new EditModelClickListener(modelData));
actionLabel = new Label(i18nManager.getMessage(Messages.MODEL_ACTION));
actionLabel.setSizeUndefined();
......@@ -253,8 +253,6 @@ public class EditorProcessDefinitionDetailPanel extends DetailPanel {
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
System.out.println("OUT ----> " + new String(bpmnBytes));
String processName = modelData.getName() + ".bpmn20.xml";
Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).addString(processName, new String(bpmnBytes)).deploy();
......
......@@ -20,19 +20,15 @@ import org.activiti.explorer.ExplorerApp;
import org.activiti.explorer.I18nManager;
import org.activiti.explorer.Messages;
import org.activiti.explorer.NotificationManager;
import org.activiti.explorer.ui.Images;
import org.activiti.explorer.ui.custom.PopupWindow;
import org.activiti.explorer.ui.mainlayout.ExplorerLayout;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
import com.vaadin.terminal.ExternalResource;
import com.vaadin.terminal.Sizeable;
import com.vaadin.terminal.UserError;
import com.vaadin.ui.AbstractLayout;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
......@@ -60,11 +56,7 @@ public class NewModelPopupWindow extends PopupWindow implements ModelDataJsonCon
protected GridLayout formLayout;
protected TextField nameTextField;
protected TextArea descriptionTextArea;
protected HorizontalLayout modelerLayout;
protected Label modelerLabel;
protected HorizontalLayout tableEditorLayout;
protected Label tableEditorLabel;
protected boolean modelerPreffered;
protected SelectEditorComponent selectEditorComponent;
protected RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
......@@ -83,7 +75,7 @@ public class NewModelPopupWindow extends PopupWindow implements ModelDataJsonCon
addStyleName(Reindeer.WINDOW_LIGHT);
setModal(true);
setWidth("460px");
setHeight("450px");
setHeight("470px");
center();
setCaption(i18nManager.getMessage(Messages.PROCESS_NEW_POPUP_CAPTION));
}
......@@ -109,129 +101,16 @@ public class NewModelPopupWindow extends PopupWindow implements ModelDataJsonCon
formLayout.addComponent(editorLabel);
formLayout.setComponentAlignment(editorLabel, Alignment.MIDDLE_LEFT);
VerticalLayout editorsLayout = new VerticalLayout();
formLayout.addComponent(editorsLayout);
createModelerEditorChoice(editorsLayout);
createTableDrivenEditorChoice(editorsLayout);
selectEditorComponent = new SelectEditorComponent();
formLayout.addComponent(selectEditorComponent);
addComponent(formLayout);
preferModeler();
// Some empty space
Label emptySpace = new Label("&nbsp;", Label.CONTENT_XHTML);
addComponent(emptySpace);
}
protected void createModelerEditorChoice(VerticalLayout editorsLayout) {
modelerLayout = new HorizontalLayout();
modelerLayout.setWidth("300px");
modelerLayout.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
editorsLayout.addComponent(modelerLayout);
modelerLayout.addListener(new LayoutClickListener() {
public void layoutClick(LayoutClickEvent event) {
preferModeler();
}
});
Button modelerButton = new Button();
modelerButton.setIcon(Images.PROCESS_EDITOR_BPMN);
modelerButton.setStyleName(Reindeer.BUTTON_LINK);
modelerLayout.addComponent(modelerButton);
modelerLayout.setComponentAlignment(modelerButton, Alignment.MIDDLE_LEFT);
modelerButton.addListener(new ClickListener() {
public void buttonClick(ClickEvent event) {
preferModeler();
}
});
VerticalLayout modelerTextLayout = new VerticalLayout();
modelerLayout.addComponent(modelerTextLayout);
modelerLayout.setExpandRatio(modelerTextLayout, 1.0f);
modelerLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_MODELER));
modelerLabel.addStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
modelerLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
modelerTextLayout.addComponent(modelerLabel);
Label modelerDescriptionLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_MODELER_DESCRIPTION));
modelerDescriptionLabel.addStyleName(Reindeer.LABEL_SMALL);
modelerDescriptionLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
modelerTextLayout.addComponent(modelerDescriptionLabel);
}
protected void createTableDrivenEditorChoice(VerticalLayout editorsLayout) {
tableEditorLayout = new HorizontalLayout();
tableEditorLayout.setWidth("300px");
tableEditorLayout.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
editorsLayout.addComponent(tableEditorLayout);
tableEditorLayout.addListener(new LayoutClickListener() {
public void layoutClick(LayoutClickEvent event) {
preferTableDrivenEditor();
}
});
Button tableEditorButton = new Button();
tableEditorButton.setIcon(Images.PROCESS_EDITOR_TABLE);
tableEditorButton.setStyleName(Reindeer.BUTTON_LINK);
tableEditorLayout.addComponent(tableEditorButton);
tableEditorLayout.setComponentAlignment(tableEditorButton, Alignment.MIDDLE_LEFT);
tableEditorButton.addListener(new ClickListener() {
public void buttonClick(ClickEvent event) {
preferTableDrivenEditor();
}
});
VerticalLayout tableEditorTextLayout = new VerticalLayout();
tableEditorLayout.addComponent(tableEditorTextLayout);
tableEditorLayout.setExpandRatio(tableEditorTextLayout, 1.0f);
tableEditorLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_TABLE));
tableEditorLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
tableEditorTextLayout.addComponent(tableEditorLabel);
Label tableEditorDescriptionLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_TABLE_DESCRIPTION));
tableEditorDescriptionLabel.addStyleName(Reindeer.LABEL_SMALL);
tableEditorDescriptionLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
tableEditorTextLayout.addComponent(tableEditorDescriptionLabel);
}
protected void preferModeler() {
if (!modelerPreffered) {
modelerPreffered = true;
selectEditor(modelerLayout);
deselectEditor(tableEditorLayout);
modelerLabel.addStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
tableEditorLabel.removeStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
}
}
protected void preferTableDrivenEditor() {
if (modelerPreffered) {
modelerPreffered = false;
selectEditor(tableEditorLayout);
deselectEditor(modelerLayout);
tableEditorLabel.addStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
modelerLabel.removeStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
}
}
protected void selectEditor(AbstractLayout editorLayout) {
editorLayout.addStyleName(ExplorerLayout.STYLE_PROCESS_EDITOR_CHOICE);
}
protected void deselectEditor(AbstractLayout editorLayout) {
editorLayout.removeStyleName(ExplorerLayout.STYLE_PROCESS_EDITOR_CHOICE);
}
protected void addButtons() {
// Create
......@@ -248,7 +127,7 @@ public class NewModelPopupWindow extends PopupWindow implements ModelDataJsonCon
return;
}
if (modelerPreffered) {
if (selectEditorComponent.isModelerPreferred()) {
try {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode editorNode = objectMapper.createObjectNode();
......
/* 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.editor.ui;
import org.activiti.explorer.ExplorerApp;
import org.activiti.explorer.I18nManager;
import org.activiti.explorer.Messages;
import org.activiti.explorer.ui.Images;
import org.activiti.explorer.ui.mainlayout.ExplorerLayout;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
import com.vaadin.ui.AbstractLayout;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.Reindeer;
/**
* @author Joram Barrez
*/
public class SelectEditorComponent extends VerticalLayout {
private static final long serialVersionUID = 1L;
protected I18nManager i18nManager;
protected boolean enableHighlightWhenClicked;
protected HorizontalLayout modelerLayout;
protected Button modelerButton;
protected Label modelerLabel;
protected Label modelerDescriptionLabel;
protected HorizontalLayout tableEditorLayout;
protected Button tableEditorButton;
protected Label tableEditorLabel;
protected Label tableEditorDescriptionLabel;
protected boolean modelerPreferred;
protected EditorSelectedListener editorSelectedListener;
public SelectEditorComponent() {
this(true);
}
public SelectEditorComponent(boolean enableHighlightWhenClicked) {
this.i18nManager = ExplorerApp.get().getI18nManager();
this.enableHighlightWhenClicked = enableHighlightWhenClicked;
createModelerEditorChoice();
addComponent(new Label("&nbsp;", Label.CONTENT_XHTML));
createTableDrivenEditorChoice();
preferModeler(); // is default to select modeler
}
protected void createModelerEditorChoice() {
modelerLayout = new HorizontalLayout();
modelerLayout.setWidth("300px");
modelerLayout.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
addComponent(modelerLayout);
modelerButton = new Button();
modelerButton.setIcon(Images.PROCESS_EDITOR_BPMN);
modelerButton.setStyleName(Reindeer.BUTTON_LINK);
modelerLayout.addComponent(modelerButton);
modelerLayout.setComponentAlignment(modelerButton, Alignment.MIDDLE_LEFT);
VerticalLayout modelerTextLayout = new VerticalLayout();
modelerLayout.addComponent(modelerTextLayout);
modelerLayout.setExpandRatio(modelerTextLayout, 1.0f);
modelerLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_MODELER));
modelerLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
modelerTextLayout.addComponent(modelerLabel);
modelerDescriptionLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_MODELER_DESCRIPTION));
modelerDescriptionLabel.addStyleName(Reindeer.LABEL_SMALL);
modelerDescriptionLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
modelerTextLayout.addComponent(modelerDescriptionLabel);
modelerLayout.addListener(new LayoutClickListener() {
public void layoutClick(LayoutClickEvent event) {
preferModeler();
}
});
modelerButton.addListener(new ClickListener() {
public void buttonClick(ClickEvent event) {
preferModeler();
}
});
}
protected void createTableDrivenEditorChoice() {
tableEditorLayout = new HorizontalLayout();
tableEditorLayout.setWidth("300px");
tableEditorLayout.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
addComponent(tableEditorLayout);
tableEditorButton = new Button();
tableEditorButton.setIcon(Images.PROCESS_EDITOR_TABLE);
tableEditorButton.setStyleName(Reindeer.BUTTON_LINK);
tableEditorLayout.addComponent(tableEditorButton);
tableEditorLayout.setComponentAlignment(tableEditorButton, Alignment.MIDDLE_LEFT);
VerticalLayout tableEditorTextLayout = new VerticalLayout();
tableEditorLayout.addComponent(tableEditorTextLayout);
tableEditorLayout.setExpandRatio(tableEditorTextLayout, 1.0f);
tableEditorLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_TABLE));
tableEditorLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
tableEditorTextLayout.addComponent(tableEditorLabel);
tableEditorDescriptionLabel = new Label(i18nManager.getMessage(Messages.PROCESS_EDITOR_TABLE_DESCRIPTION));
tableEditorDescriptionLabel.addStyleName(Reindeer.LABEL_SMALL);
tableEditorDescriptionLabel.addStyleName(ExplorerLayout.STYLE_CLICKABLE);
tableEditorTextLayout.addComponent(tableEditorDescriptionLabel);
tableEditorLayout.addListener(new LayoutClickListener() {
public void layoutClick(LayoutClickEvent event) {
preferTableDrivenEditor();
}
});
tableEditorButton.addListener(new ClickListener() {
public void buttonClick(ClickEvent event) {
preferTableDrivenEditor();
}
});
}
public void preferModeler() {
if (!modelerPreferred) {
modelerPreferred = true;
if (enableHighlightWhenClicked) {
selectEditor(modelerLayout);
deselectEditor(tableEditorLayout);
modelerLabel.addStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
tableEditorLabel.removeStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
}
}
if (editorSelectedListener != null) {
editorSelectedListener.editorSelectionChanged();
}
}
public void preferTableDrivenEditor() {
if (modelerPreferred) {
modelerPreferred = false;
if (enableHighlightWhenClicked) {
selectEditor(tableEditorLayout);
deselectEditor(modelerLayout);
tableEditorLabel.addStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
modelerLabel.removeStyleName(ExplorerLayout.STYLE_LABEL_BOLD);
}
}
if (editorSelectedListener != null) {
editorSelectedListener.editorSelectionChanged();
}
}
protected void selectEditor(AbstractLayout editorLayout) {
editorLayout.addStyleName(ExplorerLayout.STYLE_PROCESS_EDITOR_CHOICE);
}
protected void deselectEditor(AbstractLayout editorLayout) {
editorLayout.removeStyleName(ExplorerLayout.STYLE_PROCESS_EDITOR_CHOICE);
}
public HorizontalLayout getModelerLayout() {
return modelerLayout;
}
public Button getModelerButton() {
return modelerButton;
}
public HorizontalLayout getTableEditorLayout() {
return tableEditorLayout;
}
public Button getTableEditorButton() {
return tableEditorButton;
}
public Label getModelerLabel() {
return modelerLabel;
}
public Label getModelerDescriptionLabel() {
return modelerDescriptionLabel;
}
public Label getTableEditorLabel() {
return tableEditorLabel;
}
public Label getTableEditorDescriptionLabel() {
return tableEditorDescriptionLabel;
}
public boolean isModelerPreferred() {
return modelerPreferred;
}
public EditorSelectedListener getEditorSelectedListener() {
return editorSelectedListener;
}
public void setEditorSelectedListener(EditorSelectedListener editorSelectedListener) {
this.editorSelectedListener = editorSelectedListener;
}
// Helper class
public static interface EditorSelectedListener {
void editorSelectionChanged();
}
}
......@@ -47,6 +47,7 @@ import org.activiti.explorer.ui.task.InvolvedPage;
import org.activiti.explorer.ui.task.QueuedPage;
import org.activiti.explorer.ui.task.TaskMenuBar;
import org.activiti.explorer.ui.task.TasksPage;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import com.vaadin.ui.Window;
......@@ -57,8 +58,6 @@ import com.vaadin.ui.Window;
*/
public class DefaultViewManager implements ViewManager {
private static final long serialVersionUID = 1L;
protected AbstractPage currentPage;
@Autowired
......@@ -225,6 +224,10 @@ public class DefaultViewManager implements ViewManager {
switchView(new SimpleTableEditor(processName, processDescription), ViewManager.MAIN_NAVIGATION_PROCESS, null);
}
public void showSimpleTableProcessEditor(String modelId, WorkflowDefinition workflowDefinition) {
switchView(new SimpleTableEditor(modelId, workflowDefinition), ViewManager.MAIN_NAVIGATION_PROCESS, null);
}
// Management
public void showDatabasePage() {
......
......@@ -28,6 +28,8 @@ public interface Messages {
String BUTTON_OK = "button.ok";
String BUTTON_CREATE = "button.create";
String BUTTON_CANCEL = "button.cancel";
String BUTTON_SAVE = "button.save";
String BUTTON_DELETE = "button.delete";
String UNCAUGHT_EXCEPTION = "uncaught.exception";
// Navigation
......@@ -220,6 +222,7 @@ public interface Messages {
String PROCESS_EDITOR_CHOICE = "process.editor.choice";
String PROCESS_EDITOR_MODELER = "process.editor.modeler";
String PROCESS_EDITOR_MODELER_DESCRIPTION = "process.editor.modeler.description";
String PROCESS_EDITOR_CONVERSION_WARNING_MODELER = "process.editor.conversion.warning.modeler";
String PROCESS_EDITOR_TABLE = "process.editor.table";
String PROCESS_EDITOR_TABLE_DESCRIPTION = "process.editor.table.description";
String PROCESS_EDITOR_CREATE_NEW = "process.editor.create.new";
......@@ -230,6 +233,22 @@ public interface Messages {
String PROCESS_EDITOR_NAME = "process.editor.name";
String PROCESS_EDITOR_DESCRIPTION = "process.editor.description";
String PROCESS_EDITOR_TASKS = "process.editor.tasks";
String PROCESS_EDITOR_TASK_NAME = "process.editor.task.name";
String PROCESS_EDITOR_TASK_ASSIGNEE = "process.editor.task.assignee";
String PROCESS_EDITOR_TASK_GROUPS = "process.editor.task.groups";
String PROCESS_EDITOR_TASK_DESCRIPTION = "process.editor.task.description";
String PROCESS_EDITOR_TASK_CONCURRENCY = "process.editor.task.concurrency";
String PROCESS_EDITOR_TASK_START_WITH_PREVIOUS = "process.editor.task.startwithprevious";
String PROCESS_EDITOR_TASK_FORM_CREATE = "process.editor.task.form.create";
String PROCESS_EDITOR_TASK_FORM_EDIT = "process.editor.task.form.edit";
String PROCESS_EDITOR_ACTIONS = "process.editor.actions";
String PROCESS_EDITOR_PROPERTY_NAME = "process.editor.property.name";
String PROCESS_EDITOR_PROPERTY_TYPE = "process.editor.property.type";
String PROCESS_EDITOR_PROPERTY_REQUIRED = "process.editor.property.required";
String PROCESS_EDITOR_PROPERTY_TYPE_TEXT = "process.editor.property.type.text";
String PROCESS_EDITOR_PROPERTY_TYPE_NUMBER = "process.editor.property.type.number";
String PROCESS_EDITOR_PROPERTY_TYPE_DATE = "process.editor.property.type.date";
String PROCESS_EDITOR_LOADING_ERROR = "process.editor.loading.error";
String PROCESS_INSTANCE_DELETE = "process.instance.delete";
String PROCESS_INSTANCE_DELETE_POPUP_TITLE = "process.instance.delete.popup.title";
......
......@@ -13,6 +13,9 @@
package org.activiti.explorer;
import org.activiti.engine.repository.Model;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import com.vaadin.ui.Window;
......@@ -81,6 +84,8 @@ public interface ViewManager {
void showSimpleTableProcessEditor(String processName, String processDescription);
void showSimpleTableProcessEditor(String modelId, WorkflowDefinition workflowDefinition);
// Management
void showDatabasePage();
......
......@@ -10,7 +10,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.activiti.explorer.cache;
import java.util.List;
......@@ -26,8 +25,6 @@ import org.activiti.engine.identity.User;
* eg. 'Th' -> Kermit The Frog
* eg 'be' -> Fozzie Bear
*
* TODO: Should this functionality be moved to the engine?
*
* @author Joram Barrez
*/
public interface UserCache {
......
......@@ -166,7 +166,7 @@ public class ProcessInstanceDetailPanel extends DetailPanel {
imagePanel.setScrollable(true);
imagePanel.addStyleName(Reindeer.PANEL_LIGHT);
imagePanel.setWidth(100, UNITS_PERCENTAGE);
imagePanel.setHeight(400, UNITS_PIXELS);
imagePanel.setHeight(100, UNITS_PERCENTAGE);
HorizontalLayout panelLayoutT = new HorizontalLayout();
panelLayoutT.setSizeUndefined();
......
......@@ -140,7 +140,7 @@ public class ProcessDefinitionInfoComponent extends VerticalLayout {
Panel imagePanel = new Panel(); // using panel for scrollbars
imagePanel.addStyleName(Reindeer.PANEL_LIGHT);
imagePanel.setWidth(100, UNITS_PERCENTAGE);
imagePanel.setHeight(400, UNITS_PIXELS);
imagePanel.setHeight(100, UNITS_PERCENTAGE);
HorizontalLayout panelLayout = new HorizontalLayout();
panelLayout.setSizeUndefined();
imagePanel.setContent(panelLayout);
......
package org.activiti.explorer.ui.process.listener;
import java.io.IOException;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.editor.ui.SelectEditorComponent;
import org.activiti.editor.ui.SelectEditorComponent.EditorSelectedListener;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
import org.activiti.explorer.ExplorerApp;
import org.activiti.explorer.Messages;
import org.activiti.explorer.NotificationManager;
import org.activiti.explorer.ui.custom.PopupWindow;
import org.activiti.explorer.ui.mainlayout.ExplorerLayout;
import org.activiti.explorer.ui.process.simple.editor.SimpleTableEditorConstants;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.converter.json.JsonConverter;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import com.vaadin.terminal.ExternalResource;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.themes.Reindeer;
/**
* @author Tijs Rademakers
* @author Joram Barrez
*/
public class EditModelClickListener implements ClickListener {
private static final long serialVersionUID = 1L;
protected Model model;
protected NotificationManager notificationManager;
protected String modelId;
public EditModelClickListener(String modelId) {
public EditModelClickListener(Model model) {
this.notificationManager = ExplorerApp.get().getNotificationManager();
this.modelId = modelId;
this.model = model;
}
public void buttonClick(ClickEvent event) {
if (SimpleTableEditorConstants.TABLE_EDITOR_CATEGORY.equals(model.getCategory())) {
showSelectEditorPopupWindow();
} else {
showModeler();
}
}
protected WorkflowDefinition loadWorkflowDefinition() throws JsonProcessingException, IOException {
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(repositoryService.getModelEditorSource(model.getId()));
JsonConverter jsonConverter = new JsonConverter();
return jsonConverter.convertFromJson(jsonNode);
}
protected void showSelectEditorPopupWindow() {
final PopupWindow selectEditorPopupWindow = new PopupWindow();
selectEditorPopupWindow.setModal(true);
selectEditorPopupWindow.setResizable(false);
selectEditorPopupWindow.setWidth("350px");
selectEditorPopupWindow.setHeight("250px");
selectEditorPopupWindow.addStyleName(Reindeer.PANEL_LIGHT);
selectEditorPopupWindow.center();
final SelectEditorComponent selectEditorComponent = new SelectEditorComponent(false);
selectEditorComponent.getModelerDescriptionLabel().setValue(
ExplorerApp.get().getI18nManager().getMessage(Messages.PROCESS_EDITOR_CONVERSION_WARNING_MODELER));
selectEditorComponent.getModelerDescriptionLabel().addStyleName(ExplorerLayout.STYLE_LABEL_RED);
selectEditorComponent.preferTableDrivenEditor();
selectEditorPopupWindow.getContent().addComponent(selectEditorComponent);
selectEditorComponent.setEditorSelectedListener(new EditorSelectedListener() {
public void editorSelectionChanged() {
try {
WorkflowDefinition workflowDefinition = loadWorkflowDefinition();
// When using the modeler, the format must first be converted to the modeler json format
if (selectEditorComponent.isModelerPreferred()) {
WorkflowDefinitionConversion conversion = ExplorerApp.get()
.getWorkflowDefinitionConversionFactory().createWorkflowDefinitionConversion(workflowDefinition);
conversion.convert();
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
model.setCategory(null);
repositoryService.saveModel(model);
BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
ObjectNode json = bpmnJsonConverter.convertToJson(conversion.getBpmnModel());
repositoryService.addModelEditorSource(model.getId(), json.toString().getBytes("utf-8"));
// Show modeler
showModeler();
} else {
// Load and show table editor
ExplorerApp.get().getViewManager().showSimpleTableProcessEditor(model.getId(), workflowDefinition);
}
} catch (Exception e) {
e.printStackTrace();
ExplorerApp.get().getNotificationManager().showErrorNotification(Messages.PROCESS_EDITOR_LOADING_ERROR, e);
} finally {
ExplorerApp.get().getMainWindow().removeWindow(selectEditorPopupWindow);
}
}
});
ExplorerApp.get().getViewManager().showPopupWindow(selectEditorPopupWindow);
}
protected void showModeler() {
ExplorerApp.get().getMainWindow().open(new ExternalResource(
ExplorerApp.get().getURL().toString().replace("/ui", "") + "service/editor?id=" + modelId));
ExplorerApp.get().getURL().toString().replace("/ui", "") + "service/editor?id=" + model.getId()));
}
}
......@@ -13,6 +13,8 @@
package org.activiti.explorer.ui.process.simple.editor;
import org.activiti.explorer.ExplorerApp;
import org.activiti.explorer.Messages;
import org.activiti.explorer.ui.process.simple.editor.table.PropertyTable;
import org.activiti.explorer.ui.process.simple.editor.table.TaskFormModel;
import org.activiti.workflow.simple.definition.FormDefinition;
......@@ -71,7 +73,7 @@ public class FormPopupWindow extends Window {
buttons.setSpacing(true);
// Save button
Button saveButton = new Button("Save");
Button saveButton = new Button(ExplorerApp.get().getI18nManager().getMessage(Messages.BUTTON_SAVE));
buttons.addComponent(saveButton);
saveButton.addListener(new Button.ClickListener() {
......@@ -85,7 +87,7 @@ public class FormPopupWindow extends Window {
});
// Delete button
Button deleteButton = new Button("Delete");
Button deleteButton = new Button(ExplorerApp.get().getI18nManager().getMessage(Messages.BUTTON_DELETE));
buttons.addComponent(deleteButton);
deleteButton.addListener(new Button.ClickListener() {
......@@ -105,9 +107,9 @@ public class FormPopupWindow extends Window {
FormDefinition formDefinition = new FormDefinition();
for (Object itemId : propertyTable.getItemIds()) {
FormPropertyDefinition formPropertyDefinition = new FormPropertyDefinition();
formPropertyDefinition.setPropertyName((String) propertyTable.getItem(itemId).getItemProperty("property").getValue());
formPropertyDefinition.setType((String) ((ComboBox) propertyTable.getItem(itemId).getItemProperty("type").getValue()).getValue());
formPropertyDefinition.setRequired((Boolean) ((CheckBox) propertyTable.getItem(itemId).getItemProperty("required").getValue()).getValue());
formPropertyDefinition.setPropertyName((String) propertyTable.getItem(itemId).getItemProperty(PropertyTable.ID_PROPERTY_NAME).getValue());
formPropertyDefinition.setType((String) ((ComboBox) propertyTable.getItem(itemId).getItemProperty(PropertyTable.ID_PROPERTY_TYPE).getValue()).getValue());
formPropertyDefinition.setRequired((Boolean) ((CheckBox) propertyTable.getItem(itemId).getItemProperty(PropertyTable.ID_PROPERTY_REQUIRED).getValue()).getValue());
formDefinition.addFormProperty(formPropertyDefinition);
}
return formDefinition;
......
......@@ -14,10 +14,9 @@ package org.activiti.explorer.ui.process.simple.editor;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Model;
......@@ -28,9 +27,14 @@ import org.activiti.explorer.ui.custom.DetailPanel;
import org.activiti.explorer.ui.custom.ToolBar;
import org.activiti.explorer.ui.custom.ToolbarEntry.ToolbarCommand;
import org.activiti.explorer.ui.mainlayout.ExplorerLayout;
import org.activiti.explorer.ui.process.simple.editor.listener.AddTaskClickListener;
import org.activiti.explorer.ui.process.simple.editor.table.TaskTable;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.converter.json.JsonConverter;
import org.activiti.workflow.simple.definition.HumanStepDefinition;
import org.activiti.workflow.simple.definition.ParallelStepsDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.definition.StepDefinitionContainer;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.node.ObjectNode;
......@@ -47,8 +51,11 @@ import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import com.vaadin.ui.themes.Reindeer;
/**
* @author Joram Barrez
......@@ -62,32 +69,43 @@ public class SimpleTableEditor extends AbstractPage {
private static final String KEY_EDITOR = "editor";
private static final String KEY_PREVIEW = "preview";
// Input
// Input when creating new process
protected String workflowName;
protected String description;
// Input when updating existing proceess
protected String modelId;
protected WorkflowDefinition workflowDefinition;
// ui
protected DetailPanel mainLayout;
protected GridLayout editorGrid;
protected TextField nameField;
protected TextField descriptionField;
protected TaskTable taskTable;
protected Embedded diagram;
public SimpleTableEditor() {
this(null, null);
}
protected Panel imagePanel;
/**
* Constructor used when creating a new process.
*/
public SimpleTableEditor(String workflowName, String description) {
this.workflowName = workflowName;
this.description = description;
setSizeFull();
}
/**
* Constructor used when updating an existing process
*/
public SimpleTableEditor(String modelId, WorkflowDefinition workflowDefinition) {
this(workflowDefinition.getName(), workflowDefinition.getDescription());
this.modelId = modelId;
this.workflowDefinition = workflowDefinition;
}
@Override
protected void initUi() {
super.initUi();
setSizeFull();
grid.setColumnExpandRatio(0, 0f); // Hide the column on the left side
mainLayout = new DetailPanel();
......@@ -115,8 +133,8 @@ public class SimpleTableEditor extends AbstractPage {
toolBar.addToolbarEntry(KEY_EDITOR, ExplorerApp.get().getI18nManager().getMessage(Messages.PROCESS_EDITOR_TITLE), new ToolbarCommand() {
public void toolBarItemSelected() {
if (diagram != null) {
diagram.setVisible(false);
if (imagePanel != null) {
imagePanel.setVisible(false);
editorGrid.setVisible(true);
toolBar.setActiveEntry(KEY_EDITOR);
}
......@@ -163,13 +181,28 @@ public class SimpleTableEditor extends AbstractPage {
protected void initTaskTable(GridLayout layout) {
taskTable = new TaskTable();
taskTable.addDefaultTaskRow();
// TODO: taskTable.addTaskRow for existing workflow definition
// Add existing tasks in case we're editing
if (workflowDefinition != null) {
loadTaskRows(workflowDefinition, taskTable);
} else {
taskTable.addDefaultTaskRow();
}
layout.addComponent(new Label(ExplorerApp.get().getI18nManager().getMessage(Messages.PROCESS_EDITOR_TASKS)));
layout.addComponent(taskTable);
}
protected void loadTaskRows(StepDefinitionContainer<?> container, TaskTable taskTable) {
for (StepDefinition stepDefinition : container.getSteps()) {
if (stepDefinition instanceof HumanStepDefinition) {
HumanStepDefinition humanStepDefinition = (HumanStepDefinition) stepDefinition;
taskTable.addTaskRow(humanStepDefinition);
} else if (stepDefinition instanceof StepDefinitionContainer<?>) {
loadTaskRows((StepDefinitionContainer<?>) stepDefinition, taskTable);
}
}
}
protected void initButtons(GridLayout layout) {
final Button saveButton = new Button(ExplorerApp.get().getI18nManager().getMessage(Messages.PROCESS_EDITOR_SAVE));
......@@ -177,8 +210,9 @@ public class SimpleTableEditor extends AbstractPage {
toolBar.addButton(saveButton);
saveButton.addListener(new ClickListener() {
private static final long serialVersionUID = 1L;
public void buttonClick(ClickEvent event) {
saveBpmnModel();
save();
}
});
......@@ -215,19 +249,37 @@ public class SimpleTableEditor extends AbstractPage {
// resource must have unique id (or cache-crap can happen)!
StreamResource imageresource = new StreamResource(streamSource,UUID.randomUUID() + ".png", ExplorerApp.get());
diagram = new Embedded("", imageresource);
Embedded diagram = new Embedded("", imageresource);
diagram.setType(Embedded.TYPE_IMAGE);
diagram.setSizeUndefined();
mainLayout.addComponent(diagram);
imagePanel = new Panel(); // using panel for scrollbars
imagePanel.setScrollable(true);
imagePanel.addStyleName(Reindeer.PANEL_LIGHT);
imagePanel.setWidth(100, UNITS_PERCENTAGE);
imagePanel.setHeight("100%");
mainLayout.addComponent(imagePanel);
HorizontalLayout panelLayout = new HorizontalLayout();
panelLayout.setSizeUndefined();
imagePanel.setContent(panelLayout);
imagePanel.addComponent(diagram);
}
protected void saveBpmnModel() {
protected void save() {
WorkflowDefinition workflowDefinition = createWorkflow();
// We're storing the bpmn 2.0 as a model for now
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
Model model = repositoryService.newModel();
model.setName(workflowDefinition.getName());
Model model = null;
if (modelId == null) { // new process
model = repositoryService.newModel();
} else { // update existing process
model = repositoryService.getModel(modelId);
}
model.setName(workflowDefinition.getName());
model.setCategory(SimpleTableEditorConstants.TABLE_EDITOR_CATEGORY);
repositoryService.saveModel(model);
// Store model entity
......@@ -235,20 +287,20 @@ public class SimpleTableEditor extends AbstractPage {
ExplorerApp.get().getWorkflowDefinitionConversionFactory().createWorkflowDefinitionConversion(workflowDefinition);
conversion.convert();
// // Store BPMN 2.0
BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
System.out.println("IN --- > " + new String(bpmnXMLConverter.convertToXML(conversion.getBpmnModel())));
// // Store BPMN 2.0: not needed at the moment, we store the modeler json
// BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
// repositoryService.addModelEditorSource(model.getId(), bpmnXMLConverter.convertToXML(conversion.getBpmnModel()));
// Store Modeler json, so the created process can be opened by the modeler
try {
BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
ObjectNode json = bpmnJsonConverter.convertToJson(conversion.getBpmnModel());
System.out.println("JSON ---> " + json.toString());
repositoryService.addModelEditorSource(model.getId(), json.toString().getBytes("utf-8"));
// BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
// ObjectNode json = bpmnJsonConverter.convertToJson(conversion.getBpmnModel());
//
// repositoryService.addModelEditorSource(model.getId(), json.toString().getBytes("utf-8"));
JsonConverter jsonConverter = new JsonConverter();
ObjectNode json = jsonConverter.convertToJson(workflowDefinition);
repositoryService.addModelEditorSource(model.getId(), json.toString().getBytes("utf-8"));
// Store process image
// TODO: we should really allow the service to take an inputstream as input. Now we load it into memory ...
repositoryService.addModelEditorSourceExtra(model.getId(), IOUtils.toByteArray(conversion.getWorkflowDiagramImage()));
......@@ -262,10 +314,37 @@ public class SimpleTableEditor extends AbstractPage {
protected WorkflowDefinition createWorkflow() {
WorkflowDefinition workflow = new WorkflowDefinition();
workflow.setName((String) nameField.getValue());
workflow.setDescription((String) descriptionField.getValue());
for (HumanStepDefinition step : taskTable.getSteps()) {
workflow.addStep(step);
String description = (String) descriptionField.getValue();
if (description != null && description.length() > 0) {
workflow.setDescription(description);
}
List<HumanStepDefinition> steps = taskTable.getSteps();
for (int i=0; i<steps.size(); i++) {
HumanStepDefinition currentStep = steps.get(i);
// Check if we have a parallel block
int nextIndex = i+1;
ParallelStepsDefinition parallelStepsDefinition = null;
while (nextIndex < steps.size() && steps.get(nextIndex).isStartsWithPrevious()) {
if (parallelStepsDefinition == null) {
parallelStepsDefinition = new ParallelStepsDefinition();
parallelStepsDefinition.addStep(currentStep);
}
parallelStepsDefinition.addStep(steps.get(nextIndex));
nextIndex++;
}
if (parallelStepsDefinition != null) {
workflow.addStep(parallelStepsDefinition);
i = nextIndex - 1;
} else {
workflow.addStep(currentStep);
}
}
return workflow;
}
......
/* 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.explorer.ui.process.simple.editor;
/**
* @author Joram Barrez
*/
public interface SimpleTableEditorConstants {
String TABLE_EDITOR_CATEGORY = "table-editor";
}
......@@ -14,6 +14,9 @@ package org.activiti.explorer.ui.process.simple.editor.table;
import java.util.Arrays;
import org.activiti.explorer.ExplorerApp;
import org.activiti.explorer.I18nManager;
import org.activiti.explorer.Messages;
import org.activiti.explorer.ui.process.simple.editor.listener.AddPropertyClickListener;
import org.activiti.explorer.ui.process.simple.editor.listener.DeletePropertyClickListener;
......@@ -32,20 +35,31 @@ public class PropertyTable extends Table {
private static final long serialVersionUID = 6521446909987945815L;
public static final String ID_PROPERTY_NAME = "property";
public static final String ID_PROPERTY_TYPE = "type";
public static final String ID_PROPERTY_REQUIRED = "required";
public static final String ID_PROPERTY_ACTIONS = "actions";
private static final String DEFAULT_PROPERTY_NAME = "My property";
protected I18nManager i18nManager;
public PropertyTable() {
this.i18nManager = ExplorerApp.get().getI18nManager();
setEditable(true);
setColumnReorderingAllowed(true);
setPageLength(size());
addContainerProperty("property", String.class, null);
addContainerProperty("type", ComboBox.class, null);
addContainerProperty("required", CheckBox.class, null);
addContainerProperty("actions", HorizontalLayout.class, null);
addContainerProperty(ID_PROPERTY_NAME, String.class, null);
addContainerProperty(ID_PROPERTY_TYPE, ComboBox.class, null);
addContainerProperty(ID_PROPERTY_REQUIRED, CheckBox.class, null);
addContainerProperty(ID_PROPERTY_ACTIONS, HorizontalLayout.class, null);
setColumnHeader("property", "Property");
setColumnHeader("type", "Type");
setColumnHeader("required", "Required?");
setColumnHeader("actions", "Actions");
setColumnHeader(ID_PROPERTY_NAME, i18nManager.getMessage(Messages.PROCESS_EDITOR_PROPERTY_NAME));
setColumnHeader(ID_PROPERTY_TYPE, i18nManager.getMessage(Messages.PROCESS_EDITOR_PROPERTY_TYPE));
setColumnHeader(ID_PROPERTY_REQUIRED, i18nManager.getMessage(Messages.PROCESS_EDITOR_PROPERTY_REQUIRED));
setColumnHeader(ID_PROPERTY_ACTIONS, i18nManager.getMessage(Messages.PROCESS_EDITOR_ACTIONS));
}
public void addPropertyRow() {
......@@ -70,22 +84,25 @@ public class PropertyTable extends Table {
Item newItem = getItem(newItemId);
// name
newItem.getItemProperty("property").setValue(propertyName == null ? "My Property" : propertyName);
newItem.getItemProperty(ID_PROPERTY_NAME).setValue(propertyName == null ? DEFAULT_PROPERTY_NAME : propertyName);
// type
ComboBox typeComboBox = new ComboBox("types", Arrays.asList("text", "number", "date"));
ComboBox typeComboBox = new ComboBox("", Arrays.asList(
i18nManager.getMessage(Messages.PROCESS_EDITOR_PROPERTY_TYPE_TEXT),
i18nManager.getMessage(Messages.PROCESS_EDITOR_PROPERTY_TYPE_NUMBER),
i18nManager.getMessage(Messages.PROCESS_EDITOR_PROPERTY_TYPE_DATE)));
typeComboBox.setNullSelectionAllowed(false);
if (propertyType == null) {
typeComboBox.setValue(typeComboBox.getItemIds().iterator().next());
} else {
typeComboBox.setValue(propertyType);
}
newItem.getItemProperty("type").setValue(typeComboBox);
newItem.getItemProperty(ID_PROPERTY_TYPE).setValue(typeComboBox);
// required
CheckBox requiredCheckBox = new CheckBox();
requiredCheckBox.setValue(required == null ? false : required);
newItem.getItemProperty("required").setValue(requiredCheckBox);
newItem.getItemProperty(ID_PROPERTY_REQUIRED).setValue(requiredCheckBox);
// actions
HorizontalLayout actionButtons = new HorizontalLayout();
......@@ -100,7 +117,7 @@ public class PropertyTable extends Table {
addRowButton.addListener(new AddPropertyClickListener(this));
actionButtons.addComponent(addRowButton);
newItem.getItemProperty("actions").setValue(actionButtons);
newItem.getItemProperty(ID_PROPERTY_ACTIONS).setValue(actionButtons);
}
}
......@@ -27,7 +27,7 @@ public class TaskFormModel {
protected List<TaskFormModelListener> formModelListeners = new ArrayList<TaskFormModelListener>();
/* Mapping id of task in taskTable <-> FormDto */
/* Mapping id of task in taskTable <-> FormDefinition */
protected Map<Object, FormDefinition> forms = new HashMap<Object, FormDefinition>();
public void addForm(Object taskItemId, FormDefinition form) {
......
......@@ -15,6 +15,9 @@ package org.activiti.explorer.ui.process.simple.editor.table;
import java.util.ArrayList;
import java.util.List;
import org.activiti.explorer.ExplorerApp;
import org.activiti.explorer.I18nManager;
import org.activiti.explorer.Messages;
import org.activiti.explorer.ui.process.simple.editor.listener.AddTaskClickListener;
import org.activiti.explorer.ui.process.simple.editor.listener.DeleteTaskClickListener;
import org.activiti.explorer.ui.process.simple.editor.listener.ShowFormClickListener;
......@@ -35,10 +38,20 @@ import com.vaadin.ui.TextField;
public class TaskTable extends Table implements TaskFormModelListener {
private static final long serialVersionUID = -2578437667358797351L;
public static final String ID_NAME = "name";
public static final String ID_ASSIGNEE = "assignee";
public static final String ID_GROUPS = "groups";
public static final String ID_DESCRIPTION = "description";
public static final String ID_START_WITH_PREVIOUS = "startWithPrevious";
public static final String ID_ACTIONS = "actions";
protected I18nManager i18nManager;
protected TaskFormModel taskFormModel = new TaskFormModel();
public TaskTable() {
this.i18nManager = ExplorerApp.get().getI18nManager();
this.taskFormModel.addFormModelListener(this);
setEditable(true);
......@@ -47,36 +60,36 @@ public class TaskTable extends Table implements TaskFormModelListener {
setSizeFull();
setPageLength(0);
addContainerProperty("name", String.class, null);
addContainerProperty("assignee", String.class, null);
addContainerProperty("groups", String.class, null);
addContainerProperty("description", TextField.class, null);
addContainerProperty("startWithPrevious", CheckBox.class, null);
addContainerProperty("actions", HorizontalLayout.class, null);
setColumnHeader("name", "Name");
setColumnHeader("assignee", "Assignee");
setColumnHeader("groups", "Group(s)");
setColumnHeader("description", "Description");
setColumnHeader("startWithPrevious", "Concurrency");
setColumnHeader("actions", "Actions");
setColumnAlignment("name", ALIGN_CENTER);
setColumnAlignment("assignee", ALIGN_CENTER);
setColumnAlignment("groups", ALIGN_CENTER);
setColumnAlignment("description", ALIGN_CENTER);
setColumnAlignment("startsWithPrevious", ALIGN_CENTER);
setColumnWidth("actions", 170);
addContainerProperty(ID_NAME, String.class, null);
addContainerProperty(ID_ASSIGNEE, String.class, null);
addContainerProperty(ID_GROUPS, String.class, null);
addContainerProperty(ID_DESCRIPTION, TextField.class, null);
addContainerProperty(ID_START_WITH_PREVIOUS, CheckBox.class, null);
addContainerProperty(ID_ACTIONS, HorizontalLayout.class, null);
setColumnHeader(ID_NAME, i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_NAME));
setColumnHeader(ID_ASSIGNEE, i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_ASSIGNEE));
setColumnHeader(ID_GROUPS, i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_GROUPS));
setColumnHeader(ID_DESCRIPTION, i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_DESCRIPTION));
setColumnHeader(ID_START_WITH_PREVIOUS, i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_CONCURRENCY));
setColumnHeader(ID_ACTIONS, i18nManager.getMessage(Messages.PROCESS_EDITOR_ACTIONS));
setColumnAlignment(ID_NAME, ALIGN_CENTER);
setColumnAlignment(ID_ASSIGNEE, ALIGN_CENTER);
setColumnAlignment(ID_GROUPS, ALIGN_CENTER);
setColumnAlignment(ID_START_WITH_PREVIOUS, ALIGN_CENTER);
setColumnAlignment(ID_START_WITH_PREVIOUS, ALIGN_CENTER);
setColumnWidth(ID_ACTIONS, 170);
}
// TODO: reactivate when having the option to edit
// public void addTaskRow(KickstartUserTask task) {
// Object taskItemId = addTaskRow(null, task.getName(), task.getAssignee(), task.getGroups(), task.getDescription(), task.getStartsWithPrevious());
// if (task.getForm() != null) {
// taskFormModel.addForm(taskItemId, task.getForm());
// }
// }
public void addTaskRow(HumanStepDefinition humanStepDefinition) {
Object taskItemId = addTaskRow(null, humanStepDefinition.getName(), humanStepDefinition.getAssignee(),
humanStepDefinition.getCandidateGroupsCommaSeparated(), humanStepDefinition.getDescription(),
humanStepDefinition.isStartsWithPrevious());
if (humanStepDefinition.getForm() != null) {
taskFormModel.addForm(taskItemId, humanStepDefinition.getForm());
}
}
public void addDefaultTaskRow() {
addDefaultTaskRowAfter(null);
......@@ -98,13 +111,13 @@ public class TaskTable extends Table implements TaskFormModelListener {
Item newItem = getItem(newItemId);
// name
newItem.getItemProperty("name").setValue(taskName == null ? "my task" : taskName);
newItem.getItemProperty(ID_NAME).setValue(taskName == null ? "my task" : taskName);
// assignee
newItem.getItemProperty("assignee").setValue(taskAssignee == null ? "" : taskAssignee);
newItem.getItemProperty(ID_ASSIGNEE).setValue(taskAssignee == null ? "" : taskAssignee);
// groups
newItem.getItemProperty("groups").setValue(taskGroups == null ? "" : taskGroups);
newItem.getItemProperty(ID_GROUPS).setValue(taskGroups == null ? "" : taskGroups);
// description
TextField descriptionTextField = new TextField();
......@@ -113,15 +126,15 @@ public class TaskTable extends Table implements TaskFormModelListener {
if (taskDescription != null) {
descriptionTextField.setValue(taskDescription);
}
newItem.getItemProperty("description").setValue(descriptionTextField);
newItem.getItemProperty(ID_DESCRIPTION).setValue(descriptionTextField);
// concurrency
CheckBox startWithPreviousCheckBox = new CheckBox("start with previous");
CheckBox startWithPreviousCheckBox = new CheckBox(i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_START_WITH_PREVIOUS));
startWithPreviousCheckBox.setValue(startWithPrevious == null ? false : startWithPrevious);
newItem.getItemProperty("startWithPrevious").setValue(startWithPreviousCheckBox);
newItem.getItemProperty(ID_START_WITH_PREVIOUS).setValue(startWithPreviousCheckBox);
// actions
newItem.getItemProperty("actions").setValue(generateActionButtons(newItemId));
newItem.getItemProperty(ID_ACTIONS).setValue(generateActionButtons(newItemId));
return newItemId;
}
......@@ -130,7 +143,8 @@ public class TaskTable extends Table implements TaskFormModelListener {
HorizontalLayout actionButtons = new HorizontalLayout();
FormDefinition form = taskFormModel.getForm(taskItemId);
Button formButton = new Button(form == null ? "Create form" : "Edit form");
Button formButton = new Button(form == null ? i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_FORM_CREATE) :
i18nManager.getMessage(Messages.PROCESS_EDITOR_TASK_FORM_EDIT));
formButton.addListener(new ShowFormClickListener(taskFormModel, taskItemId));
formButton.setData(taskItemId);
actionButtons.addComponent(formButton);
......@@ -154,12 +168,35 @@ public class TaskTable extends Table implements TaskFormModelListener {
Item item = getItem(itemId);
HumanStepDefinition humanStepDefinition = new HumanStepDefinition();
humanStepDefinition.setName((String) item.getItemProperty("name").getValue());
humanStepDefinition.setAssignee((String) item.getItemProperty("assignee").getValue());
// humanStepDefinition.setGroups((String) item.getItemProperty("groups").getValue());
humanStepDefinition.setDescription((String) ((TextField) item.getItemProperty("description").getValue()).getValue());
// humanStepDefinition.setStartWithPrevious((boolean) ((CheckBox) item.getItemProperty("startWithPrevious").getValue()).booleanValue());
humanStepDefinition.setForm(taskFormModel.getForm(itemId));
String name = (String) item.getItemProperty(ID_NAME).getValue();
if (name != null && name.length() > 0) {
humanStepDefinition.setName(name);
}
String assignee = (String) item.getItemProperty(ID_ASSIGNEE).getValue();
if (assignee != null && assignee.length() > 0) {
humanStepDefinition.setAssignee(assignee);
}
String groups = (String) item.getItemProperty("groups").getValue();
List<String> candidateGroups = new ArrayList<String>();
if (groups != null && groups.length() > 0) {
for (String group : groups.split(",")) {
candidateGroups.add(group.trim());
}
}
humanStepDefinition.setCandidateGroups(candidateGroups);
String description = (String) ((TextField) item.getItemProperty(ID_DESCRIPTION).getValue()).getValue();
if (description != null && description.length() > 0) {
humanStepDefinition.setDescription(description);
}
humanStepDefinition.setStartsWithPrevious((boolean) ((CheckBox) item.getItemProperty(ID_START_WITH_PREVIOUS).getValue()).booleanValue());
FormDefinition formDefinition = taskFormModel.getForm(itemId);
humanStepDefinition.setForm(formDefinition);
steps.add(humanStepDefinition);
}
......@@ -168,12 +205,12 @@ public class TaskTable extends Table implements TaskFormModelListener {
/** Implements FormModelListener */
public void formAdded(Object taskItemId) {
getItem(taskItemId).getItemProperty("actions").setValue(generateActionButtons(taskItemId));
getItem(taskItemId).getItemProperty(ID_ACTIONS).setValue(generateActionButtons(taskItemId));
}
/** Implements FormModelListener */
public void formRemoved(Object taskItemId) {
getItem(taskItemId).getItemProperty("actions").setValue(generateActionButtons(taskItemId));
getItem(taskItemId).getItemProperty(ID_ACTIONS).setValue(generateActionButtons(taskItemId));
}
}
......@@ -7,6 +7,8 @@ confirmation.dialog.no= No
button.ok = Ok
button.create = Create
button.cancel = Cancel
button.save = Save
button.delete = Delete
uncaught.exception = Warning!
# Navigation
......@@ -202,6 +204,7 @@ process.toxml.failed = Create of BPMN XML failed
process.editor.choice = Preferred editor:
process.editor.modeler = Activiti Modeler
process.editor.modeler.description = Full-blown BPMN 2.0 graphical modeling environment. Choose this to have all features of Activiti at your fingertips.
process.editor.conversion.warning.modeler = Warning: once you choose to open this model with the Activiti Modeler, it will not be possible to edit it again with the table-driven editor.
process.editor.table = Table-driven definition
process.editor.table.description = Simplified editor using a intuitive table-based approach. Use this if you need to quickly develop simple workflow or if you don't know BPMN 2.0.
process.editor.create.new = Creating new process "{0}"
......@@ -212,6 +215,22 @@ process.editor.description = Description
process.editor.tasks = Tasks
process.editor.bpmn.preview = Diagram preview
process.editor.save = Save
process.editor.property.name = Name
process.editor.property.type = Type
process.editor.property.required = Required?
process.editor.task.name = Name
process.editor.task.assignee = Assignee
process.editor.task.groups = Group(s)
process.editor.task.description = Description
process.editor.task.concurrency = Concurrency
process.editor.task.startwithprevious = start with previous
process.editor.task.form.create = Create form
process.editor.task.form.edit = Edit form
process.editor.actions = Actions
process.editor.property.type.text = text
process.editor.property.type.number = number
process.editor.property.type.date = date
process.editor.loading.error = Could not load process
process.instance.delete = Delete
process.instance.delete.popup.title = Delete process instance {0}?
process.instance.delete.popup.description = Are you sure you want to delete process instance {0}?<br/>This will also delete all associated tasks and data.
......
......@@ -28,10 +28,12 @@ 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.
* Instances of this class are created by a {@link WorkflowDefinitionConversionFactory}.
*
* @see StepDefinitionConverter
* An instance of this class is capabale of doing the actual conversion of a {@link WorkflowDefinition}
* and it will contain all artifacts produces by the {@link StepDefinitionConverter} objects and
* {@link WorkflowDefinitionConversionListener} which were injected into
* the {@link WorkflowDefinitionConversionFactory}.
*
* @author Frederik Heremans
* @author Joram Barrez
......@@ -44,6 +46,12 @@ public class WorkflowDefinitionConversion {
// Artifacts of the conversion
protected BpmnModel bpmnModel;
protected Process process;
/**
* It is assumed the conversion will always create a {@link BpmnModel} and a {@link Process}
* (altough, strictly even that is pluggable). Other artifacts that are produced
* during conversion are stored in this generic map.
*/
protected Map<String, Object> additionalArtifacts;
// Helper members
......@@ -51,6 +59,7 @@ public class WorkflowDefinitionConversion {
protected String lastActivityId;
protected HashMap<String, Integer> incrementalIdMapping;
// Properties to influence the conversion
protected boolean sequenceflowGenerationEnabled = true;
protected boolean updateLastActivityEnabled = true;
......@@ -64,6 +73,10 @@ public class WorkflowDefinitionConversion {
this.workflowDefinition = workflowDefinition;
}
/**
* Call this method to actually execute the conversion of the {@link WorkflowDefinition}
* which was provided in the constructor.
*/
public void convert() {
if (workflowDefinition == null) {
......@@ -79,17 +92,21 @@ public class WorkflowDefinitionConversion {
bpmnModel.addProcess(process);
// Let conversion listeners know initialization is finished
for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getWorkflowDefinitionConversionListeners()) {
conversionListener.beforeStepsConversion(this);
if (conversionFactory.getWorkflowDefinitionConversionListeners() != null) {
for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getWorkflowDefinitionConversionListeners()) {
conversionListener.beforeStepsConversion(this);
}
}
// Convert each step
convertSteps(workflowDefinition.getSteps());
// Let conversion listeners know step conversion is done
for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getWorkflowDefinitionConversionListeners()) {
conversionListener.afterStepsConversion(this);
}
if (conversionFactory.getWorkflowDefinitionConversionListeners() != null) {
for (WorkflowDefinitionConversionListener conversionListener : conversionFactory.getWorkflowDefinitionConversionListeners()) {
conversionListener.afterStepsConversion(this);
}
}
// Add DI information to bpmn model
WorkflowDIGenerator workflowDIGenerator = new WorkflowDIGenerator(bpmnModel);
......@@ -188,11 +205,22 @@ public class WorkflowDefinitionConversion {
this.updateLastActivityEnabled = updateLastActivityEnabled;
}
/**
* Returns the BPMN 2.0 xml which is the converted version of the
* provided {@link WorkflowDefinition}.
*/
public String getbpm20Xml() {
if (bpmnModel == null) {
convert();
}
BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
return new String(bpmnXMLConverter.convertToXML(bpmnModel));
}
/**
* Returns the BPMN 2.0 diagram which is the converted version of the
* provided {@link WorkflowDefinition}.
*/
public InputStream getWorkflowDiagramImage() {
if (bpmnModel == null) {
convert();
......
......@@ -21,8 +21,11 @@ import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
/**
* Factory that is capable of creating {@link WorkflowDefinitionConversion}
* objects.
* Factory that is capable of creating {@link WorkflowDefinitionConversion} objects.
*
* It is necessary for a correct conversion to set (or inject) {@link StepDefinition} converters
* (instances of {@link StepDefinitionConverter}) and conversion life cycle listeners
* (instance of {@link WorkflowDefinitionConversionListener}).
*
* @author Frederik Heremans
* @author Joram Barrez
......
......@@ -12,79 +12,148 @@
*/
package org.activiti.workflow.simple.converter.json;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.activiti.engine.ActivitiException;
import org.activiti.workflow.simple.definition.AbstractNamedStepDefinition;
import org.activiti.workflow.simple.definition.FormDefinition;
import org.activiti.workflow.simple.definition.FormPropertyDefinition;
import org.activiti.workflow.simple.definition.HumanStepDefinition;
import org.activiti.workflow.simple.definition.ParallelStepsDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.definition.StepDefinitionContainer;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;
/**
* @author Joram Barrez
*/
@Deprecated // Dunno if I actually need this in Activiti?
public class JsonConverter {
private static final String WORKFLOW_NAME = "name";
private static final String WORKFLOW_DESCRIPTION = "description";
private static final String WORKFLOW_STEPS = "steps";
public static final String WORKFLOW_NAME = "name";
public static final String WORKFLOW_DESCRIPTION = "description";
public static final String WORKFLOW_STEPS = "steps";
private static final String STEP_TYPE = "type";
public static final String STEP_TYPE = "type";
private static final String STEP_TYPE_HUMAN_STEP = "human-step";
public static final String STEP_TYPE_HUMAN_STEP = "human-step";
private static final String HUMAN_STEP_NAME = "name";
private static final String HUMAN_STEP_DESCRIPTION = "description";
private static final String HUMAN_STEP_ASSIGNEE = "assignee";
public static final String HUMAN_STEP_NAME = "name";
public static final String HUMAN_STEP_DESCRIPTION = "description";
public static final String HUMAN_STEP_ASSIGNEE = "assignee";
public static final String HUMAN_STEP_GROUPS = "groups";
public WorkflowDefinition convertJson(ObjectNode json) {
public static final String FORM = "form";
public static final String FORM_PROPERTY_NAME = "name";
public static final String FORM_PROPERTY_TYPE = "type";
public static final String FORM_PROPERTY_MANDATORY = "mandatory";
public WorkflowDefinition convertFromJson(JsonNode json) {
WorkflowDefinition workflowDefinition = new WorkflowDefinition();
// Name and description
workflowDefinition.setName(getStringFieldValue(json, WORKFLOW_NAME, true));
workflowDefinition.setDescription(getStringFieldValue(json, WORKFLOW_DESCRIPTION, false));
String description = getStringFieldValue(json, WORKFLOW_DESCRIPTION, false);
if (description != null) {
workflowDefinition.setDescription(description);
}
ArrayNode stepsArray = getArray(json, WORKFLOW_STEPS, true);
Iterator<JsonNode> stepIterator = stepsArray.iterator();
while (stepIterator.hasNext()) {
convertStep(workflowDefinition, stepIterator.next());
convertAndAddStepDefinition(workflowDefinition, stepIterator.next());
}
return workflowDefinition;
}
protected void convertStep(StepDefinitionContainer<?> stepDefinitionContainer, JsonNode stepJsonNode) {
protected void convertAndAddStepDefinition(StepDefinitionContainer<?> stepDefinitionContainerToAddTo, JsonNode stepJsonNode) {
if (stepJsonNode.isArray()) {
stepDefinitionContainer.addStep(convertParallelSteps((ArrayNode) stepJsonNode));
stepDefinitionContainerToAddTo.addStep(convertToParallelStepsDefinition((ArrayNode) stepJsonNode));
} else {
String type = getStringFieldValue(stepJsonNode, STEP_TYPE, true);
if (STEP_TYPE_HUMAN_STEP.equals(type)) {
stepDefinitionContainer.addStep(convertHumanStepJson(stepJsonNode));
stepDefinitionContainerToAddTo.addStep(convertToHumanStepDefinition(stepJsonNode));
}
}
}
protected ParallelStepsDefinition convertParallelSteps(ArrayNode stepArray) {
protected ParallelStepsDefinition convertToParallelStepsDefinition(ArrayNode stepArray) {
ParallelStepsDefinition parallelStepsDefinition = new ParallelStepsDefinition();
Iterator<JsonNode> stepIterator = stepArray.iterator();
while(stepIterator.hasNext()) {
convertStep(parallelStepsDefinition, stepIterator.next());
convertAndAddStepDefinition(parallelStepsDefinition, stepIterator.next());
}
// Must set startWithPrevious good (a bit hacky ...)
for (int i=0; i<parallelStepsDefinition.getSteps().size(); i++) {
StepDefinition stepDefinition = parallelStepsDefinition.getSteps().get(i);
if (i > 0 && stepDefinition instanceof AbstractNamedStepDefinition) {
((AbstractNamedStepDefinition) stepDefinition).setStartsWithPrevious(true);
}
}
return parallelStepsDefinition;
}
protected HumanStepDefinition convertHumanStepJson(JsonNode humanStepJson) {
protected HumanStepDefinition convertToHumanStepDefinition(JsonNode humanStepJson) {
HumanStepDefinition humanStepDefinition = new HumanStepDefinition();
humanStepDefinition.setName(getStringFieldValue(humanStepJson, HUMAN_STEP_NAME, false));
humanStepDefinition.setDescription(getStringFieldValue(humanStepJson, HUMAN_STEP_DESCRIPTION, false));
humanStepDefinition.setAssignee(getStringFieldValue(humanStepJson, HUMAN_STEP_ASSIGNEE, false));
String name = getStringFieldValue(humanStepJson, HUMAN_STEP_NAME, false);
if (name != null) {
humanStepDefinition.setName(name);
}
String description = getStringFieldValue(humanStepJson, HUMAN_STEP_DESCRIPTION, false);
if (description != null) {
humanStepDefinition.setDescription(description);
}
String assignee = getStringFieldValue(humanStepJson, HUMAN_STEP_ASSIGNEE, false);
if (assignee != null) {
humanStepDefinition.setAssignee(assignee);
}
// Candidate groups
ArrayNode groupArray = getArray(humanStepJson, HUMAN_STEP_GROUPS, false);
if (groupArray != null && groupArray.size() > 0) {
List<String> groups = new ArrayList<String>();
Iterator<JsonNode> groupIterator = groupArray.iterator();
while (groupIterator.hasNext()) {
JsonNode groupNode = groupIterator.next();
groups.add(groupNode.getTextValue());
}
humanStepDefinition.setCandidateGroups(groups);
}
// Form
ArrayNode formPropertyArray = getArray(humanStepJson, FORM, false);
if (formPropertyArray != null) {
Iterator<JsonNode> formProperyIterator = formPropertyArray.iterator();
FormDefinition formDefinition = new FormDefinition();
while (formProperyIterator.hasNext()) {
JsonNode formPropertyJsonNode = formProperyIterator.next();
FormPropertyDefinition propertyDefinition = new FormPropertyDefinition();
propertyDefinition.setPropertyName(getStringFieldValue(formPropertyJsonNode, FORM_PROPERTY_NAME, true));
propertyDefinition.setType(getStringFieldValue(formPropertyJsonNode, FORM_PROPERTY_TYPE, true));
propertyDefinition.setRequired(getBooleanValue(formPropertyJsonNode, FORM_PROPERTY_MANDATORY, false, false));
formDefinition.addFormProperty(propertyDefinition);
}
humanStepDefinition.setForm(formDefinition);
}
return humanStepDefinition;
}
// Json Helper methods
protected String getStringFieldValue(JsonNode json, String field, boolean mandatory) {
if (json.has(field)) {
JsonNode fieldNode = json.get(field);
......@@ -98,6 +167,30 @@ public class JsonConverter {
}
}
protected boolean getBooleanValue(JsonNode json, String field, boolean mandatory, boolean defaultValue) {
if (json.has(field)) {
return json.get(field).asBoolean();
} else {
if (mandatory) {
throw new ActivitiException("Could not convert json: " + field + "is mandatory on " + json.toString());
} else {
return defaultValue;
}
}
}
protected JsonNode getObject(JsonNode json, String field, boolean mandatory) {
if (json.has(field)) {
return json.get(field);
} else {
if (mandatory) {
throw new ActivitiException("Could not convert json: " + field + " is mandatory on " + json.toString());
} else {
return null;
}
}
}
protected ArrayNode getArray(JsonNode json, String arrayName, boolean mandatory) {
if (json.has(arrayName)) {
JsonNode arrayJsonNode = json.get(arrayName);
......@@ -115,4 +208,93 @@ public class JsonConverter {
}
}
// Conversion to JSON ------------------------------------------------------------------------
public ObjectNode convertToJson(WorkflowDefinition workflowDefinition) {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode json = objectMapper.createObjectNode();
json.put(WORKFLOW_NAME, workflowDefinition.getName());
if (workflowDefinition.getDescription() != null) {
json.put(WORKFLOW_DESCRIPTION, workflowDefinition.getDescription());
}
ArrayNode steps = json.putArray(WORKFLOW_STEPS);
for (JsonNode stepNode : convertToJson(objectMapper, workflowDefinition.getSteps())) {
steps.add(stepNode);
}
return json;
}
protected List<JsonNode> convertToJson(ObjectMapper objectMapper, List<StepDefinition> steps) {
List<JsonNode> convertedNodes = new ArrayList<JsonNode>();
for (StepDefinition stepDefinition : steps) {
convertedNodes.add(convertToJson(objectMapper, stepDefinition));
}
return convertedNodes;
}
protected JsonNode convertToJson(ObjectMapper objectMapper, StepDefinition stepDefinition) {
if (stepDefinition instanceof ParallelStepsDefinition) {
return convertToJson(objectMapper, (ParallelStepsDefinition) stepDefinition);
} else if (stepDefinition instanceof HumanStepDefinition) {
return convertToJson(objectMapper, (HumanStepDefinition) stepDefinition);
} else {
throw new ActivitiException("Unknown step definition type " + stepDefinition.getClass().getName() + ": cannot complete json conversion");
}
}
protected ArrayNode convertToJson(ObjectMapper objectMapper, ParallelStepsDefinition parallelStepsDefinition) {
ArrayNode parallelSteps = objectMapper.createArrayNode();
for (JsonNode jsonNode : convertToJson(objectMapper, parallelStepsDefinition.getSteps())) {
parallelSteps.add(jsonNode);
}
return parallelSteps;
}
protected JsonNode convertToJson(ObjectMapper objectMapper, HumanStepDefinition humanStepDefinition) {
ObjectNode humanStepNode = objectMapper.createObjectNode();
humanStepNode.put(HUMAN_STEP_NAME, humanStepDefinition.getName());
humanStepNode.put(STEP_TYPE, STEP_TYPE_HUMAN_STEP);
// Description
if (humanStepDefinition.getDescription() != null) {
humanStepNode.put(HUMAN_STEP_DESCRIPTION, humanStepDefinition.getDescription());
}
// Assignee
if (humanStepDefinition.getAssignee() != null) {
humanStepNode.put(HUMAN_STEP_ASSIGNEE, humanStepDefinition.getAssignee());
}
// Candidate groups
if (humanStepDefinition.getCandidateGroups() != null && humanStepDefinition.getCandidateGroups().size() > 0) {
ArrayNode groups = humanStepNode.putArray(HUMAN_STEP_GROUPS);
for (String group : humanStepDefinition.getCandidateGroups()) {
groups.add(group);
}
}
// Form
if (humanStepDefinition.getForm() != null) {
FormDefinition formDefinition = humanStepDefinition.getForm();
ArrayNode form = humanStepNode.putArray(FORM);
if (formDefinition.getFormProperties() != null && formDefinition.getFormProperties().size() > 0) {
for (FormPropertyDefinition propertyDefinition : formDefinition.getFormProperties()) {
ObjectNode formPropertyNode = objectMapper.createObjectNode();
formPropertyNode.put(FORM_PROPERTY_NAME, propertyDefinition.getPropertyName());
formPropertyNode.put(FORM_PROPERTY_TYPE, propertyDefinition.getType());
formPropertyNode.put(FORM_PROPERTY_MANDATORY, propertyDefinition.isRequired());
form.add(formPropertyNode);
}
}
}
return humanStepNode;
}
}
......@@ -12,15 +12,30 @@
*/
package org.activiti.workflow.simple.converter.listener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.activiti.bpmn.model.EndEvent;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.workflow.simple.converter.ConversionConstants;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversionFactory;
import org.activiti.workflow.simple.definition.WorkflowDefinition;
import org.activiti.workflow.simple.util.BpmnModelUtil;
/**
* Default listener for {@link WorkflowDefinitionConversion} lifecycle events.
*
* When added to a {@link WorkflowDefinitionConversionFactory}, this class will make sure
* a start event and end event is created for the {@link Process}.
* Further, it will generate a correct incoming and outgoing sequence flow list
* for each {@link Process} element, as required by some toolings (eg Modeler).
*
* @author Joram Barrez
*/
public class DefaultWorkflowDefinitionConversionListener implements WorkflowDefinitionConversionListener
......@@ -78,6 +93,79 @@ public class DefaultWorkflowDefinitionConversionListener implements WorkflowDefi
sequenceFlow.setSourceRef(conversion.getLastActivityId());
sequenceFlow.setTargetRef(END_EVENT_ID);
process.addFlowElement(sequenceFlow);
// To make the generated workflow compatible with some tools (eg the Modeler, but also others),
// We must add the ingoing and outgoing sequence flow to each of the flow nodes
SequenceFlowMapping sequenceFlowMapping = generateSequenceflowMappings(process);
for (FlowNode flowNode : BpmnModelUtil.findFlowElementsOfType(process, FlowNode.class)) {
List<SequenceFlow> incomingSequenceFlow = sequenceFlowMapping.getIncomingSequenceFlowMapping().get(flowNode.getId());
if (incomingSequenceFlow != null) {
flowNode.setIncomingFlows(incomingSequenceFlow);
}
List<SequenceFlow> outgoingSequenceFlow = sequenceFlowMapping.getOutgoingSequenceFlowMapping().get(flowNode.getId());
if (outgoingSequenceFlow != null) {
flowNode.setOutgoingFlows(outgoingSequenceFlow);
}
}
}
protected SequenceFlowMapping generateSequenceflowMappings(Process process) {
HashMap<String, List<SequenceFlow>> incomingSequenceFlowMapping = new HashMap<String, List<SequenceFlow>>();
HashMap<String, List<SequenceFlow>> outgoingSequenceFlowMapping = new HashMap<String, List<SequenceFlow>>();
for (FlowElement flowElement : BpmnModelUtil.findFlowElementsOfType(process, SequenceFlow.class)) {
SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
String srcId = sequenceFlow.getSourceRef();
String targetId = sequenceFlow.getTargetRef();
if (outgoingSequenceFlowMapping.get(srcId) == null) {
outgoingSequenceFlowMapping.put(srcId, new ArrayList<SequenceFlow>());
}
outgoingSequenceFlowMapping.get(srcId).add(sequenceFlow);
if (incomingSequenceFlowMapping.get(targetId) == null) {
incomingSequenceFlowMapping.put(targetId, new ArrayList<SequenceFlow>());
}
incomingSequenceFlowMapping.get(targetId).add(sequenceFlow);
}
SequenceFlowMapping mapping = new SequenceFlowMapping();
mapping.setIncomingSequenceFlowMapping(incomingSequenceFlowMapping);
mapping.setOutgoingSequenceFlowMapping(outgoingSequenceFlowMapping);
return mapping;
}
static class SequenceFlowMapping {
protected HashMap<String, List<SequenceFlow>> incomingSequenceFlowMapping;
protected HashMap<String, List<SequenceFlow>> outgoingSequenceFlowMapping;
public HashMap<String, List<SequenceFlow>> getIncomingSequenceFlowMapping() {
return incomingSequenceFlowMapping;
}
public void setIncomingSequenceFlowMapping(HashMap<String, List<SequenceFlow>> incomingSequenceFlowMapping) {
if (incomingSequenceFlowMapping != null) {
this.incomingSequenceFlowMapping = incomingSequenceFlowMapping;
} else {
this.incomingSequenceFlowMapping = new HashMap<String, List<SequenceFlow>>();
}
}
public HashMap<String, List<SequenceFlow>> getOutgoingSequenceFlowMapping() {
return outgoingSequenceFlowMapping;
}
public void setOutgoingSequenceFlowMapping(HashMap<String, List<SequenceFlow>> outgoingSequenceFlowMapping) {
if (outgoingSequenceFlowMapping != null) {
this.outgoingSequenceFlowMapping = outgoingSequenceFlowMapping;
} else {
this.outgoingSequenceFlowMapping = new HashMap<String, List<SequenceFlow>>();
}
}
}
}
......@@ -13,14 +13,31 @@
package org.activiti.workflow.simple.converter.listener;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversionFactory;
import org.activiti.workflow.simple.converter.step.StepDefinitionConverter;
/**
* Allows to hook into the lifecycle of a {@link WorkflowDefinitionConversion}.
*
* Instances of this class can be added to a {@link WorkflowDefinitionConversionFactory}
* and the specific methods will be called during the conversion.
*
* @see DefaultWorkflowDefinitionConversionListener
*
* @author Joram Barrez
*/
public interface WorkflowDefinitionConversionListener {
/**
* Called when the {@link WorkflowDefinitionConversion} is initialized,
* but nothing has been converted yet.
*/
void beforeStepsConversion(WorkflowDefinitionConversion conversion);
/**
* Called when the {@link WorkflowDefinitionConversion} has called all
* {@link StepDefinitionConverter} that were added to the {@link WorkflowDefinitionConversionFactory}.
*/
void afterStepsConversion(WorkflowDefinitionConversion conversion);
}
......@@ -13,15 +13,21 @@
package org.activiti.workflow.simple.converter.step;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.workflow.simple.converter.ConversionConstants;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.definition.StepDefinition;
/**
* Base class that can be used for {@link StepDefinitionConverter}, contains
* utility-methods.
*
* Base class that can be used for {@link StepDefinitionConverter}, contains utility-methods.
*
* All {@link StepDefinitionConverter} should extend this class and implement any BPMN 2.0 xml
* generation logic in the {@link #createProcessArtifact(StepDefinition, WorkflowDefinitionConversion)} method.
*
* The generation of additional artifacts should be done by overriding the {@link #createAdditionalArtifacts(Object)}
* method adn adding the produced artifacts to the generic map on the {@link WorkflowDefinitionConversion}.
*
* @author Frederik Heremans
* @author Joram Barrez
*/
......@@ -46,6 +52,11 @@ public abstract class BaseStepDefinitionConverter<U extends StepDefinition, T> i
protected void createAdditionalArtifacts(T defaultGeneratedArtifact) {
}
/**
* Adds a flow element to the {@link Process}.
* A sequence flow from the last known element to this element will be generated,
* unless the sequence flow generation is globally disabled.
*/
protected void addFlowElement(WorkflowDefinitionConversion conversion, FlowElement flowElement) {
addFlowElement(conversion, flowElement, true);
}
......
/* 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.step;
/**
* @author Joram Barrez
*/
public interface DefaultFormPropertyTypes {
String TEXT = "string";
String NUMBER = "number";
String DATE = "date";
}
......@@ -12,12 +12,17 @@
*/
package org.activiti.workflow.simple.converter.step;
import org.activiti.bpmn.model.FormProperty;
import org.activiti.bpmn.model.UserTask;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
import org.activiti.workflow.simple.definition.FormDefinition;
import org.activiti.workflow.simple.definition.FormPropertyDefinition;
import org.activiti.workflow.simple.definition.HumanStepDefinition;
import org.activiti.workflow.simple.definition.StepDefinition;
/**
* {@link StepDefinitionConverter} for converting a {@link HumanStepDefinition} to a {@link UserTask}.
*
* @author Frederik Heremans
* @author Joram Barrez
*/
......@@ -47,21 +52,51 @@ public class HumanStepDefinitionConverter extends BaseStepDefinitionConverter<Hu
userTask.setName(humanStepDefinition.getName());
userTask.setDocumentation(humanStepDefinition.getDescription());
// Initiator
if (humanStepDefinition.isAssigneeInitiator()) {
userTask.setAssignee(INITIATOR_ASSIGNEE_EXPRESSION);
// Assignee
} else if (humanStepDefinition.getAssignee() != null) {
userTask.setAssignee(humanStepDefinition.getAssignee());
}
// Candidate Users
if (humanStepDefinition.getCandidateUsers() != null && humanStepDefinition.getCandidateUsers().size() > 0) {
userTask.setCandidateUsers(humanStepDefinition.getCandidateUsers());
}
// Candidate groups
if (humanStepDefinition.getCandidateGroups() != null && humanStepDefinition.getCandidateGroups().size() > 0) {
userTask.setCandidateGroups(humanStepDefinition.getCandidateGroups());
}
// Form
if (humanStepDefinition.getForm() != null) {
FormDefinition formDefinition = humanStepDefinition.getForm();
// Form properties
for (FormPropertyDefinition propertyDefinition : formDefinition.getFormProperties()) {
FormProperty formProperty = new FormProperty();
formProperty.setId(propertyDefinition.getPropertyName());
formProperty.setName(propertyDefinition.getPropertyName());
formProperty.setRequired(propertyDefinition.isRequired());
String type = DefaultFormPropertyTypes.TEXT;
if (DefaultFormPropertyTypes.NUMBER.equals(propertyDefinition.getType())) {
type = "long";
} else if (DefaultFormPropertyTypes.DATE.equals(propertyDefinition.getType())) {
type = "date";
}
formProperty.setType(type);
userTask.getFormProperties().add(formProperty);
}
}
return userTask;
}
}
......@@ -22,6 +22,14 @@ import org.activiti.workflow.simple.definition.StepDefinition;
import org.activiti.workflow.simple.util.BpmnModelUtil;
/**
* {@link StepDefinitionConverter} for converting a {@link ParallelStepsDefinition} to the following BPMN 2.0 structure:
*
* __ t1___
* | |
* + -- ...---+-
* | |
* - txxx---
*
* @author Joram Barrez
*/
public class ParallelStepsDefinitionConverter extends BaseStepDefinitionConverter<ParallelStepsDefinition, ParallelGateway> {
......
......@@ -18,8 +18,7 @@ import org.activiti.workflow.simple.definition.StepDefinition;
/**
* <p>
* A class that is responsible for converting a single {@link StepDefinition} to
* all required artifacts needed. This includes process-elements, content-types
* and forms.
* all required artifacts needed.
* </p>
*
* <p>
......@@ -43,7 +42,7 @@ public interface StepDefinitionConverter {
* @param stepDefinition
* the {@link StepDefinition}
* @param conversion
* context to add artifacts to
* The conversion which is calling this step converter.
*/
void convertStepDefinition(StepDefinition stepDefinition, WorkflowDefinitionConversion conversion);
......
......@@ -13,12 +13,15 @@
package org.activiti.workflow.simple.definition;
/**
* Superclass for all {@link StepDefinition} classes that have a name or description.
*
* @author Joram Barrez
*/
public abstract class AbstractNamedStepDefinition implements StepDefinition {
protected String name;
protected String description;
protected boolean startsWithPrevious;
public String getName() {
return name;
......@@ -36,4 +39,12 @@ public abstract class AbstractNamedStepDefinition implements StepDefinition {
this.description = description;
}
public boolean isStartsWithPrevious() {
return startsWithPrevious;
}
public void setStartsWithPrevious(boolean startsWithPrevious) {
this.startsWithPrevious = startsWithPrevious;
}
}
......@@ -16,12 +16,15 @@ import java.util.ArrayList;
import java.util.List;
/**
* A {@link HumanStepDefinition} can have a form associated with it
* that a user must complete to continue the workflow.
* Such a form contains {@link FormPropertyDefinition} and potentially
* a form key, when the properties are not used.
*
* @author Joram Barrez
*/
public class FormDefinition {
protected String formKey;
protected List<FormPropertyDefinition> formProperties = new ArrayList<FormPropertyDefinition>();
public List<FormPropertyDefinition> getFormProperties() {
......@@ -36,12 +39,4 @@ public class FormDefinition {
formProperties.add(formProperty);
}
public String getFormKey() {
return formKey;
}
public void setFormKey(String formKey) {
this.formKey = formKey;
}
}
......@@ -13,6 +13,8 @@
package org.activiti.workflow.simple.definition;
/**
* Defines one property in a {@link FormDefinition} that is associated with a {@link HumanStepDefinition}.
*
* @author Joram Barrez
*/
public class FormPropertyDefinition {
......
......@@ -15,18 +15,16 @@ package org.activiti.workflow.simple.definition;
import java.util.List;
/**
* Defines a step that must be executed by a human actor.
*
* @author Joram Barrez
*/
public class HumanStepDefinition extends AbstractNamedStepDefinition {
protected String assignee;
protected boolean isAssigneeInitiator = false;
protected List<String> candidateUsers;
protected List<String> candidateGroups;
protected FormDefinition form;
public boolean isAssigneeInitiator() {
......@@ -56,6 +54,19 @@ public class HumanStepDefinition extends AbstractNamedStepDefinition {
public List<String> getCandidateGroups() {
return candidateGroups;
}
public String getCandidateGroupsCommaSeparated() {
if (candidateGroups == null || candidateGroups.size() == 0) {
return null;
}
StringBuilder strb = new StringBuilder();
for (String group : candidateGroups) {
strb.append(group + ", ");
}
strb.delete(strb.length() - 2, strb.length());
return strb.toString();
}
public void setCandidateGroups(List<String> candidateGroups) {
this.candidateGroups = candidateGroups;
......
......@@ -15,6 +15,8 @@ package org.activiti.workflow.simple.definition;
import org.activiti.engine.ActivitiException;
/**
* Defines a block of steps that all must be executed in parallel.
*
* @author Joram Barrez
*/
public class ParallelStepsDefinition extends AbstractStepDefinitionContainer<ParallelStepsDefinition> implements StepDefinition {
......
......@@ -13,6 +13,8 @@
package org.activiti.workflow.simple.definition;
/**
* Marker interface for all 'patterns' that are known by the simple workflow API.
*
* @author Joram Barrez
*/
public interface StepDefinition {
......
......@@ -12,9 +12,40 @@
*/
package org.activiti.workflow.simple.definition;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.workflow.simple.converter.WorkflowDefinitionConversion;
/**
* Allows to create simple workflows through an easy, fluent Java API.
*
* For example:
*
* WorkflowDefinition workflowDefinition = new WorkflowDefinition()
* .name("testWorkflow")
* .description("This is a test workflow")
* .inParallel()
* .addHumanStep("first task", "kermit")
* .addHumanStep("second step", "gonzo")
* .addHumanStep("thrid task", "mispiggy")
* .endParallel()
* .addHumanStep("Task in between", "kermit")
* .inParallel()
* .addHumanStep("fourth task", "gonzo")
* .addHumanStep("fifth step", "gonzo")
* .endParallel();
*
* Feed this {@link WorkflowDefinition} instance to a {@link WorkflowDefinitionConversion}
* and it will convert it a to a {@link BpmnModel}, which in it turn can be used
* to generate valid BPMN 2.0 through the {@link BpmnXMLConverter}.
*
* The reason why we're not just using the {@link BpmnModel} and it's related
* classes to generate bpmn 2.0 xml, is because this class and it's related classes
* are a layer on top of them, allowing to easily create patterns. Such patterns are
* for example a parallel block ({@link ParallelStepsDefinition}) or a choice step.
* These can be expressed in their {@link BpmnModel} counterpart of course,
* but these abstraction are much easier to read and use.
*
* @author Joram Barrez
*/
public class WorkflowDefinition extends AbstractStepDefinitionContainer<WorkflowDefinition> {
......
......@@ -40,7 +40,7 @@ import org.activiti.workflow.simple.util.BpmnModelUtil;
* @author Joram Barrez
*/
public class WorkflowDIGenerator {
// Constants
protected static final int SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH = 45;
protected static final int ARROW_WIDTH = 5;
......@@ -70,7 +70,6 @@ public class WorkflowDIGenerator {
protected int startY;
protected int currentWidth;
protected ProcessDiagramCanvas processDiagramCanvas;
// protected List<BlockOfSteps> allStepBlocks;
protected Map<String, List<SequenceFlow>> outgoingSequenceFlowMapping;
protected Map<String, List<SequenceFlow>> incomingSequenceFlowMapping;
protected Set<String> handledElements;
......@@ -111,15 +110,13 @@ public class WorkflowDIGenerator {
// Enough preparation, actually draw some stuff
for (FlowElement flowElement : process.getFlowElements()) {
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
......@@ -128,7 +125,6 @@ public class WorkflowDIGenerator {
} else if (flowElement instanceof ParallelGateway
&& outgoingSequenceFlowMapping.get(flowElement.getId()).size() > 1) { // fork
ParallelGateway parallelGateway = (ParallelGateway) flowElement;
drawSequenceFlow(incomingSequenceFlowMapping.get(flowElement.getId()).get(0),
generateImage,
......@@ -243,26 +239,28 @@ public class WorkflowDIGenerator {
// 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);
int taskStartY = currentHeight - ((TASK_HEIGHT + TASK_HEIGHT_SPACING) / 2);
drawTask(userTask, centerOfRhombus + SEQUENCE_FLOW_WIDTH, taskStartY, TASK_WIDTH, TASK_HEIGHT, generateImage);
handledElements.add(userTask.getId());
int sequenceFlowY = taskStartY + TASK_HEIGHT/2;
drawSequenceFlow(sequenceFlow1, generateImage, centerOfRhombus, y, centerOfRhombus, sequenceFlowY,
centerOfRhombus + SEQUENCE_FLOW_WIDTH, sequenceFlowY);
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, sequenceFlowY,
seqFlowX + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, sequenceFlowY,
seqFlowX + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, y);
currentHeight += TASK_HEIGHT + TASK_HEIGHT_SPACING;
......@@ -296,21 +294,22 @@ public class WorkflowDIGenerator {
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 taskY = currentHeight - ((TASK_HEIGHT + TASK_HEIGHT_SPACING) / 2);
drawTask(userTask, centerOfRhombus + SEQUENCE_FLOW_WIDTH, taskY, TASK_WIDTH, TASK_HEIGHT, generateImage);
int sequenceFlowY = taskY + TASK_HEIGHT/2;
drawSequenceFlow(sequenceFlow1, generateImage,
centerOfRhombus, y + GATEWAY_HEIGHT, centerOfRhombus,
sequenceFlowY, centerOfRhombus + SEQUENCE_FLOW_WIDTH, sequenceFlowY);
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
seqFlowX, sequenceFlowY,
seqFlowX + LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, sequenceFlowY, seqFlowX
+ LONG_SEQUENCE_FLOW_WITHOUT_ARROW_WIDTH, y + GATEWAY_HEIGHT);
handledElements.add(userTask.getId());
......
......@@ -115,7 +115,7 @@ public class WorkflowConversionTest {
}
@Test
public void testThreeUserTasksInParallel() {
public void testThreeUserTasksInParallel() throws Exception {
TaskService taskService = activitiRule.getTaskService();
WorkflowDefinition workflowDefinition = new WorkflowDefinition()
......
......@@ -66,9 +66,6 @@ public class SpringJobExecutor extends JobExecutor {
@Override
protected void stopExecutingJobs() {
stopJobAcquisitionThread();
taskExecutor = null;
}
}
......@@ -108,7 +108,7 @@
<property name="environment" value="${activiti.ui.environment}" />
</bean>
<bean name="workflowDefinitionConversionFactory" class="org.activiti.workflow.simple.converter.WorkflowDefinitionConversionFactory">
<bean name="workflowDefinitionConversionFactory" class="org.activiti.workflow.simple.converter.WorkflowDefinitionConversionFactory">
<property name="stepDefinitionConverters">
<list>
<bean class="org.activiti.workflow.simple.converter.step.HumanStepDefinitionConverter" />
......
......@@ -5,7 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init" destroy-method="close">
<bean id="dataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" destroy-method="close">
<property name="className" value="oracle.jdbc.xa.client.OracleXADataSource" />
<property name="uniqueName" value="xaOracle" />
<property name="maxPoolSize" value="20" />
......@@ -13,8 +13,6 @@
<property name="driverProperties">
<!-- Oracle -->
<props>
<prop key="serverName">@jdbc.servername@</prop>
<prop key="databaseName">@jdbc.databasename@</prop>
<prop key="user">@jdbc.username@</prop>
<prop key="password">@jdbc.password@</prop>
<prop key="URL">@jdbc.url@</prop>
......@@ -29,6 +27,12 @@
<property name="logPart1Filename" value="target/btm1.log"/>
<property name="logPart2Filename" value="target/btm2.log"/>
</bean>
<!-- Listener to close datasource when engine is closed, spring's destroy-method is never called... -->
<bean id="lifecycleListener" class="org.activiti.standalone.jta.CloseXADataSourceLifecycleListener" depends-on="btmConfig">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
</bean>
<!-- create BTM transaction manager -->
<bean id="transactionManager" factory-method="getTransactionManager" class="bitronix.tm.TransactionManagerServices"
......@@ -40,6 +44,8 @@
<property name="databaseSchemaUpdate" value="true" />
<property name="jobExecutorActivate" value="false" />
<property name="mailServerPort" value="5025" />
<property name="processEngineLifecycleListener" ref="lifecycleListener" />
</bean>
</beans>
......@@ -193,7 +193,7 @@ ProcessEngine processEngine = ProcessEngineConfiguration
<programlisting>historyService
.createHistoricDetailQuery()
.onlyFormProperties()
.formProperties()
...
.list();</programlisting>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册