Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/* 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.bpmn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.swing.SwingConstants;
import org.activiti.bpmn.model.Artifact;
import org.activiti.bpmn.model.Association;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BoundaryEvent;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.CallActivity;
import org.activiti.bpmn.model.DataObject;
import org.activiti.bpmn.model.Event;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowElementsContainer;
import org.activiti.bpmn.model.Gateway;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.SubProcess;
import org.activiti.bpmn.model.Task;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxEdgeStyle;
import com.mxgraph.view.mxGraph;
import org.activiti.bpmn.model.TextAnnotation;
/**
* Auto layouts a {@link BpmnModel}.
*
*/
public class BpmnAutoLayout {
private static final String STYLE_EVENT = "styleEvent";
private static final String STYLE_GATEWAY = "styleGateway";
private static final String STYLE_SEQUENCEFLOW = "styleSequenceFlow";
private static final String STYLE_BOUNDARY_SEQUENCEFLOW = "styleBoundarySequenceFlow";
protected BpmnModel bpmnModel;
protected int eventSize = 30;
protected int gatewaySize = 40;
protected int taskWidth = 100;
protected int taskHeight = 60;
protected int subProcessMargin = 20;
protected mxGraph graph;
protected Object cellParent;
protected Map associations;
protected Map textAnnotations;
protected Map sequenceFlows;
protected List boundaryEvents;
protected Map handledFlowElements;
protected Map handledArtifacts;
protected Map generatedVertices;
protected Map generatedSequenceFlowEdges;
protected Map generatedAssociationEdges;
public BpmnAutoLayout(BpmnModel bpmnModel) {
this.bpmnModel = bpmnModel;
}
public void execute() {
// Reset any previous DI information
bpmnModel.getLocationMap().clear();
bpmnModel.getFlowLocationMap().clear();
// Generate DI for each process
for (Process process : bpmnModel.getProcesses()) {
layout(process);
// Operations that can only be done after all elements have received
// DI
translateNestedSubprocesses(process);
}
}
protected void layout(FlowElementsContainer flowElementsContainer) {
graph = new mxGraph();
cellParent = graph.getDefaultParent();
graph.getModel().beginUpdate();
// Subprocesses are handled in a new instance of BpmnAutoLayout, hence they instantiations of new maps here.
handledFlowElements = new HashMap();
handledArtifacts = new HashMap();
generatedVertices = new HashMap();
generatedSequenceFlowEdges = new HashMap();
generatedAssociationEdges = new HashMap();
associations = new HashMap(); //Associations are gathered and processed afterwards, because we must be sure we already found source and target
textAnnotations = new HashMap(); // Text Annotations are gathered and processed afterwards, because we must be sure we already found the parent.
sequenceFlows = new HashMap(); // Sequence flow are gathered and processed afterwards,because we mustbe sure we already found source and target
boundaryEvents = new ArrayList(); // Boundary events are gathered and processed afterwards, because we must be sure we have its parent
// Process all elements
for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
if (flowElement instanceof SequenceFlow) {
handleSequenceFlow((SequenceFlow) flowElement);
} else if (flowElement instanceof Event) {
handleEvent(flowElement);
} else if (flowElement instanceof Gateway) {
createGatewayVertex(flowElement);
} else if (flowElement instanceof Task || flowElement instanceof CallActivity) {
handleActivity(flowElement);
} else if (flowElement instanceof SubProcess) {
handleSubProcess(flowElement);
}
handledFlowElements.put(flowElement.getId(), flowElement);
}
// process artifacts
for (Artifact artifact : flowElementsContainer.getArtifacts()) {
if (artifact instanceof Association) {
handleAssociation((Association) artifact);
} else if (artifact instanceof TextAnnotation) {
handleTextAnnotation((TextAnnotation) artifact);
}
handledArtifacts.put(artifact.getId(), artifact);
}
// Process gathered elements
handleBoundaryEvents();
handleSequenceFlow();
handleAssociations();
// All elements are now put in the graph. Let's layout them!
CustomLayout layout = new CustomLayout(graph, SwingConstants.WEST);
layout.setIntraCellSpacing(100.0);
layout.setResizeParent(true);
layout.setFineTuning(true);
layout.setParentBorder(20);
layout.setMoveParent(true);
layout.setDisableEdgeStyle(false);
layout.setUseBoundingBox(true);
layout.execute(graph.getDefaultParent());
graph.getModel().endUpdate();
generateDiagramInterchangeElements();
}
private void handleTextAnnotation(TextAnnotation artifact) {
ensureArtifactIdSet(artifact);
textAnnotations.put(artifact.getId(), artifact);
}
// BPMN element handling
protected void ensureSequenceFlowIdSet(SequenceFlow sequenceFlow) {
// We really must have ids for sequence flow to be able to generate
// stuff
if (sequenceFlow.getId() == null) {
sequenceFlow.setId("sequenceFlow-" + UUID.randomUUID().toString());
}
}
protected void ensureArtifactIdSet(Artifact artifact) {
// We really must have ids for sequence flow to be able to generate stuff
if (artifact.getId() == null) {
artifact.setId("artifact-" + UUID.randomUUID().toString());
}
}
protected void handleAssociation(Association association) {
ensureArtifactIdSet(association);
associations.put(association.getId(), association);
}
protected void handleSequenceFlow(SequenceFlow sequenceFlow) {
ensureSequenceFlowIdSet(sequenceFlow);
sequenceFlows.put(sequenceFlow.getId(), sequenceFlow);
}
protected void handleEvent(FlowElement flowElement) {
// Boundary events are an exception to the general way of drawing an
// event
if (flowElement instanceof BoundaryEvent) {
boundaryEvents.add((BoundaryEvent) flowElement);
} else {
createEventVertex(flowElement);
}
}
protected void handleActivity(FlowElement flowElement) {
Object activityVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, taskWidth, taskHeight);
generatedVertices.put(flowElement.getId(), activityVertex);
}
protected void handleSubProcess(FlowElement flowElement) {
BpmnAutoLayout bpmnAutoLayout = new BpmnAutoLayout(bpmnModel);
bpmnAutoLayout.layout((SubProcess) flowElement);
double subProcessWidth = bpmnAutoLayout.getGraph().getView().getGraphBounds().getWidth();
double subProcessHeight = bpmnAutoLayout.getGraph().getView().getGraphBounds().getHeight();
Object subProcessVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, subProcessWidth + 2 * subProcessMargin, subProcessHeight + 2 * subProcessMargin);
generatedVertices.put(flowElement.getId(), subProcessVertex);
}
protected void handleBoundaryEvents() {
for (BoundaryEvent boundaryEvent : boundaryEvents) {
mxGeometry geometry = new mxGeometry(0.8, 1.0, eventSize, eventSize);
geometry.setOffset(new mxPoint(-(eventSize / 2), -(eventSize / 2)));
geometry.setRelative(true);
mxCell boundaryPort = new mxCell(null, geometry, "shape=ellipse;perimter=ellipsePerimeter");
boundaryPort.setId("boundary-event-" + boundaryEvent.getId());
boundaryPort.setVertex(true);
Object portParent = null;
if (boundaryEvent.getAttachedToRefId() != null) {
portParent = generatedVertices.get(boundaryEvent.getAttachedToRefId());
} else if (boundaryEvent.getAttachedToRef() != null) {
portParent = generatedVertices.get(boundaryEvent.getAttachedToRef().getId());
} else {
throw new RuntimeException("Could not generate DI: boundaryEvent '" + boundaryEvent.getId() + "' has no attachedToRef");
}
graph.addCell(boundaryPort, portParent);
generatedVertices.put(boundaryEvent.getId(), boundaryPort);
}
}
protected void handleSequenceFlow() {
Hashtable edgeStyle = new Hashtable();
edgeStyle.put(mxConstants.STYLE_ORTHOGONAL, true);
edgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.ElbowConnector);
edgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.0);
edgeStyle.put(mxConstants.STYLE_ENTRY_Y, 0.5);
graph.getStylesheet().putCellStyle(STYLE_SEQUENCEFLOW, edgeStyle);
Hashtable boundaryEdgeStyle = new Hashtable();
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.OrthConnector);
graph.getStylesheet().putCellStyle(STYLE_BOUNDARY_SEQUENCEFLOW, boundaryEdgeStyle);
for (SequenceFlow sequenceFlow : sequenceFlows.values()) {
Object sourceVertex = generatedVertices.get(sequenceFlow.getSourceRef());
Object targetVertex = generatedVertices.get(sequenceFlow.getTargetRef());
String style = null;
if (handledFlowElements.get(sequenceFlow.getSourceRef()) instanceof BoundaryEvent) {
// Sequence flow out of boundary events are handled in a
// different way,
// to make them visually appealing for the eye of the dear end
// user.
style = STYLE_BOUNDARY_SEQUENCEFLOW;
} else {
style = STYLE_SEQUENCEFLOW;
}
Object sequenceFlowEdge = graph.insertEdge(cellParent, sequenceFlow.getId(), "", sourceVertex, targetVertex, style);
generatedSequenceFlowEdges.put(sequenceFlow.getId(), sequenceFlowEdge);
}
}
protected void handleAssociations() {
Hashtable edgeStyle = new Hashtable();
edgeStyle.put(mxConstants.STYLE_ORTHOGONAL, true);
edgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.ElbowConnector);
edgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.0);
edgeStyle.put(mxConstants.STYLE_ENTRY_Y, 0.5);
graph.getStylesheet().putCellStyle(STYLE_SEQUENCEFLOW, edgeStyle);
Hashtable boundaryEdgeStyle = new Hashtable();
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_EXIT_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_X, 0.5);
boundaryEdgeStyle.put(mxConstants.STYLE_ENTRY_Y, 1.0);
boundaryEdgeStyle.put(mxConstants.STYLE_EDGE, mxEdgeStyle.OrthConnector);
graph.getStylesheet().putCellStyle(STYLE_BOUNDARY_SEQUENCEFLOW, boundaryEdgeStyle);
for (Association association : associations.values()) {
Object sourceVertex = generatedVertices.get(association.getSourceRef());
Object targetVertex = generatedVertices.get(association.getTargetRef());
String style = null;
if (handledFlowElements.get(association.getSourceRef()) instanceof BoundaryEvent) {
// Sequence flow out of boundary events are handled in a different way,
// to make them visually appealing for the eye of the dear end user.
style = STYLE_BOUNDARY_SEQUENCEFLOW;
} else {
style = STYLE_SEQUENCEFLOW;
}
Object associationEdge = graph.insertEdge(cellParent, association.getId(), "", sourceVertex, targetVertex, style);
generatedAssociationEdges.put(association.getId(), associationEdge);
}
}
// Graph cell creation
protected void createEventVertex(FlowElement flowElement) {
// Add styling for events if needed
if (!graph.getStylesheet().getStyles().containsKey(STYLE_EVENT)) {
Hashtable eventStyle = new Hashtable();
eventStyle.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
graph.getStylesheet().putCellStyle(STYLE_EVENT, eventStyle);
}
// Add vertex representing event to graph
Object eventVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, eventSize, eventSize, STYLE_EVENT);
generatedVertices.put(flowElement.getId(), eventVertex);
}
protected void createGatewayVertex(FlowElement flowElement) {
// Add styling for gateways if needed
if (graph.getStylesheet().getStyles().containsKey(STYLE_GATEWAY)) {
Hashtable style = new Hashtable();
style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_RHOMBUS);
graph.getStylesheet().putCellStyle(STYLE_GATEWAY, style);
}
// Create gateway node
Object gatewayVertex = graph.insertVertex(cellParent, flowElement.getId(), "", 0, 0, gatewaySize, gatewaySize, STYLE_GATEWAY);
generatedVertices.put(flowElement.getId(), gatewayVertex);
}
// Diagram interchange generation
protected void generateDiagramInterchangeElements() {
generateActivityDiagramInterchangeElements();
generateSequenceFlowDiagramInterchangeElements();
generateAssociationDiagramInterchangeElements();
}
protected void generateActivityDiagramInterchangeElements() {
for (String flowElementId : generatedVertices.keySet()) {
Object vertex = generatedVertices.get(flowElementId);
mxCellState cellState = graph.getView().getState(vertex);
GraphicInfo subProcessGraphicInfo = createDiagramInterchangeInformation(handledFlowElements.get(flowElementId), (int) cellState.getX(), (int) cellState.getY(), (int) cellState.getWidth(),
(int) cellState.getHeight());
// The DI for the elements of a subprocess are generated without
// knowledge of the rest of the graph
// So we must translate all it's elements with the x and y of the
// subprocess itself
if (handledFlowElements.get(flowElementId) instanceof SubProcess) {
// Always expanded when auto layouting
subProcessGraphicInfo.setExpanded(true);
}
}
}
protected void generateSequenceFlowDiagramInterchangeElements() {
for (String sequenceFlowId : generatedSequenceFlowEdges.keySet()) {
Object edge = generatedSequenceFlowEdges.get(sequenceFlowId);
List points = graph.getView().getState(edge).getAbsolutePoints();
// JGraphX has this funny way of generating the outgoing sequence
// flow of a gateway
// Visually, we'd like them to originate from one of the corners of
// the rhombus,
// hence we force the starting point of the sequence flow to the
// closest rhombus corner point.
FlowElement sourceElement = handledFlowElements.get(sequenceFlows.get(sequenceFlowId).getSourceRef());
if (sourceElement instanceof Gateway && ((Gateway) sourceElement).getOutgoingFlows().size() > 1) {
mxPoint startPoint = points.get(0);
Object gatewayVertex = generatedVertices.get(sourceElement.getId());
mxCellState gatewayState = graph.getView().getState(gatewayVertex);
mxPoint northPoint = new mxPoint(gatewayState.getX() + (gatewayState.getWidth()) / 2, gatewayState.getY());
mxPoint southPoint = new mxPoint(gatewayState.getX() + (gatewayState.getWidth()) / 2, gatewayState.getY() + gatewayState.getHeight());
mxPoint eastPoint = new mxPoint(gatewayState.getX() + gatewayState.getWidth(), gatewayState.getY() + (gatewayState.getHeight()) / 2);
mxPoint westPoint = new mxPoint(gatewayState.getX(), gatewayState.getY() + (gatewayState.getHeight()) / 2);
double closestDistance = Double.MAX_VALUE;
mxPoint closestPoint = null;
for (mxPoint rhombusPoint : Arrays.asList(northPoint, southPoint, eastPoint, westPoint)) {
double distance = euclidianDistance(startPoint, rhombusPoint);
if (distance < closestDistance) {
closestDistance = distance;
closestPoint = rhombusPoint;
}
}
startPoint.setX(closestPoint.getX());
startPoint.setY(closestPoint.getY());
// We also need to move the second point.
// Since we know the layout is from left to right, this is not a
// problem
if (points.size() > 1) {
mxPoint nextPoint = points.get(1);
nextPoint.setY(closestPoint.getY());
}
}
createDiagramInterchangeInformation(handledFlowElements.get(sequenceFlowId), optimizeEdgePoints(points));
}
}
protected void generateAssociationDiagramInterchangeElements() {
for (String associationId : generatedAssociationEdges.keySet()) {
Object edge = generatedAssociationEdges.get(associationId);
List points = graph.getView().getState(edge).getAbsolutePoints();
createDiagramInterchangeInformation(handledArtifacts.get(associationId), optimizeEdgePoints(points));
}
}
protected double euclidianDistance(mxPoint point1, mxPoint point2) {
return Math.sqrt(((point2.getX() - point1.getX()) * (point2.getX() - point1.getX()) + (point2.getY() - point1.getY()) * (point2.getY() - point1.getY())));
}
// JGraphX sometime generates points that visually are not really necessary.
// This method will remove any such points.
protected List optimizeEdgePoints(List unoptimizedPointsList) {
List optimizedPointsList = new ArrayList();
for (int i = 0; i < unoptimizedPointsList.size(); i++) {
boolean keepPoint = true;
mxPoint currentPoint = unoptimizedPointsList.get(i);
// When three points are on the same x-axis with same y value, the
// middle point can be removed
if (i > 0 && i != unoptimizedPointsList.size() - 1) {
mxPoint previousPoint = unoptimizedPointsList.get(i - 1);
mxPoint nextPoint = unoptimizedPointsList.get(i + 1);
if (currentPoint.getX() >= previousPoint.getX() && currentPoint.getX() <= nextPoint.getX() && currentPoint.getY() == previousPoint.getY() && currentPoint.getY() == nextPoint.getY()) {
keepPoint = false;
} else if (currentPoint.getY() >= previousPoint.getY() && currentPoint.getY() <= nextPoint.getY() && currentPoint.getX() == previousPoint.getX() && currentPoint.getX() == nextPoint.getX()) {
keepPoint = false;
}
}
if (keepPoint) {
optimizedPointsList.add(currentPoint);
}
}
return optimizedPointsList;
}
protected GraphicInfo createDiagramInterchangeInformation(FlowElement flowElement, int x, int y, int width, int height) {
GraphicInfo graphicInfo = new GraphicInfo();
graphicInfo.setX(x);
graphicInfo.setY(y);
graphicInfo.setWidth(width);
graphicInfo.setHeight(height);
graphicInfo.setElement(flowElement);
bpmnModel.addGraphicInfo(flowElement.getId(), graphicInfo);
return graphicInfo;
}
protected void createDiagramInterchangeInformation(BaseElement element, List waypoints) {
List graphicInfoForWaypoints = new ArrayList();
for (mxPoint waypoint : waypoints) {
GraphicInfo graphicInfo = new GraphicInfo();
graphicInfo.setElement(element);
graphicInfo.setX(waypoint.getX());
graphicInfo.setY(waypoint.getY());
graphicInfoForWaypoints.add(graphicInfo);
}
bpmnModel.addFlowGraphicInfoList(element.getId(), graphicInfoForWaypoints);
}
/**
* Since subprocesses are autolayouted independently (see {@link #handleSubProcess(FlowElement)}), the elements have x and y coordinates relative to the bounds of the subprocess (thinking the
* subprocess is on (0,0). This however, does not work for nested subprocesses, as they need to take in account the x and y coordinates for each of the parent subproceses.
*
* This method is to be called after fully layouting one process, since ALL elements need to have x and y.
*/
protected void translateNestedSubprocesses(Process process) {
for (FlowElement flowElement : process.getFlowElements()) {
if (flowElement instanceof SubProcess) {
translateNestedSubprocessElements((SubProcess) flowElement);
}
}
}
protected void translateNestedSubprocessElements(SubProcess subProcess) {
GraphicInfo subProcessGraphicInfo = bpmnModel.getLocationMap().get(subProcess.getId());
double subProcessX = subProcessGraphicInfo.getX();
double subProcessY = subProcessGraphicInfo.getY();
List nestedSubProcesses = new ArrayList();
for (FlowElement flowElement : subProcess.getFlowElements()) {
if (flowElement instanceof SequenceFlow) {
List graphicInfos = bpmnModel.getFlowLocationMap().get(flowElement.getId());
for (GraphicInfo graphicInfo : graphicInfos) {
graphicInfo.setX(graphicInfo.getX() + subProcessX + subProcessMargin);
graphicInfo.setY(graphicInfo.getY() + subProcessY + subProcessMargin);
}
} else if (flowElement instanceof DataObject == false) {
// Regular element
GraphicInfo graphicInfo = bpmnModel.getLocationMap().get(flowElement.getId());
graphicInfo.setX(graphicInfo.getX() + subProcessX + subProcessMargin);
graphicInfo.setY(graphicInfo.getY() + subProcessY + subProcessMargin);
}
if (flowElement instanceof SubProcess) {
nestedSubProcesses.add((SubProcess) flowElement);
}
}
// Continue for next level of nested subprocesses
for (SubProcess nestedSubProcess : nestedSubProcesses) {
translateNestedSubprocessElements(nestedSubProcess);
}
}
// Getters and Setters
public mxGraph getGraph() {
return graph;
}
public void setGraph(mxGraph graph) {
this.graph = graph;
}
public int getEventSize() {
return eventSize;
}
public void setEventSize(int eventSize) {
this.eventSize = eventSize;
}
public int getGatewaySize() {
return gatewaySize;
}
public void setGatewaySize(int gatewaySize) {
this.gatewaySize = gatewaySize;
}
public int getTaskWidth() {
return taskWidth;
}
public void setTaskWidth(int taskWidth) {
this.taskWidth = taskWidth;
}
public int getTaskHeight() {
return taskHeight;
}
public void setTaskHeight(int taskHeight) {
this.taskHeight = taskHeight;
}
public int getSubProcessMargin() {
return subProcessMargin;
}
public void setSubProcessMargin(int subProcessMargin) {
this.subProcessMargin = subProcessMargin;
}
// Due to a bug (see
// http://forum.jgraph.com/questions/5952/mxhierarchicallayout-not-correct-when-using-child-vertex)
// We must extend the default hierarchical layout to tweak it a bit (see url
// link) otherwise the layouting crashes.
//
// Verify again with a later release if fixed (ie the mxHierarchicalLayout
// can be used directly)
static class CustomLayout extends mxHierarchicalLayout {
public CustomLayout(mxGraph graph, int orientation) {
super(graph, orientation);
this.traverseAncestors = false;
}
}
}