
org.activiti.engine.impl.bpmn.diagram.ProcessDiagramLayoutFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activiti-engine Show documentation
Show all versions of activiti-engine Show documentation
workflow engine base on bboss and activiti.
The newest version!
/* 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.impl.bpmn.diagram;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.bpmn.parser.BpmnParser;
import org.activiti.engine.repository.DiagramElement;
import org.activiti.engine.repository.DiagramLayout;
import org.activiti.engine.repository.DiagramNode;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Provides positions and dimensions of elements in a process diagram as
* provided by {@link RepositoryService#getProcessDiagram(String)}.
*
* @author Falko Menge
*/
public class ProcessDiagramLayoutFactory {
private static final int GREY_THRESHOLD = 175;
/**
* Provides positions and dimensions of elements in a process diagram as
* provided by {@link RepositoryService#getProcessDiagram(String)}.
*
* Currently, it only supports BPMN 2.0 models.
*
* @param bpmnXmlStream
* BPMN 2.0 XML file
* @param imageStream
* BPMN 2.0 diagram in PNG format (JPEG and other formats supported
* by {@link ImageIO} may also work)
* @return Layout of the process diagram
* @return null when parameter imageStream is null
*/
public DiagramLayout getProcessDiagramLayout(InputStream bpmnXmlStream, InputStream imageStream) {
Document bpmnModel = parseXml(bpmnXmlStream);
return getBpmnProcessDiagramLayout(bpmnModel, imageStream);
}
/**
* Provides positions and dimensions of elements in a BPMN process diagram as
* provided by {@link RepositoryService#getProcessDiagram(String)}.
*
* @param bpmnXmlStream
* BPMN 2.0 XML document
* @param imageStream
* BPMN 2.0 diagram in PNG format (JPEG and other formats supported
* by {@link ImageIO} may also work)
* @return Layout of the process diagram
* @return null when parameter imageStream is null
*/
public DiagramLayout getBpmnProcessDiagramLayout(Document bpmnModel, InputStream imageStream) {
if (imageStream == null) {
return null;
}
DiagramNode diagramBoundsXml = getDiagramBoundsFromBpmnDi(bpmnModel);
DiagramNode diagramBoundsImage;
if (isExportedFromAdonis50(bpmnModel)) {
int offsetTop = 29; // Adonis header
int offsetBottom = 61; // Adonis footer
diagramBoundsImage = getDiagramBoundsFromImage(imageStream, offsetTop, offsetBottom);
} else {
diagramBoundsImage = getDiagramBoundsFromImage(imageStream);
}
Map listOfBounds = new HashMap();
listOfBounds.put(diagramBoundsXml.getId(), diagramBoundsXml);
// listOfBounds.putAll(getElementBoundsFromBpmnDi(bpmnModel));
listOfBounds.putAll(fixFlowNodePositionsIfModelFromAdonis(bpmnModel, getElementBoundsFromBpmnDi(bpmnModel)));
Map listOfBoundsForImage = transformBoundsForImage(diagramBoundsImage, diagramBoundsXml, listOfBounds);
return new DiagramLayout(listOfBoundsForImage);
}
protected Document parseXml(InputStream bpmnXmlStream) {
// Initiate DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Get one that understands namespaces
factory.setNamespaceAware(true);
DocumentBuilder builder;
Document bpmnModel;
try {
// Get DocumentBuilder
builder = factory.newDocumentBuilder();
// Parse and load the Document into memory
bpmnModel = builder.parse(bpmnXmlStream);
} catch (Exception e) {
throw new ActivitiException("Error while parsing BPMN model.", e);
}
return bpmnModel;
}
protected DiagramNode getDiagramBoundsFromBpmnDi(Document bpmnModel) {
Double minX = null;
Double minY = null;
Double maxX = null;
Double maxY = null;
// Node positions and dimensions
NodeList setOfBounds = bpmnModel.getElementsByTagNameNS(BpmnParser.BPMN_DC_NS, "Bounds");
for (int i = 0; i < setOfBounds.getLength(); i++) {
Element element = (Element) setOfBounds.item(i);
Double x = Double.valueOf(element.getAttribute("x"));
Double y = Double.valueOf(element.getAttribute("y"));
Double width = Double.valueOf(element.getAttribute("width"));
Double height = Double.valueOf(element.getAttribute("height"));
if (x == 0.0 && y == 0.0 && width == 0.0 && height == 0.0) {
// Ignore empty labels like the ones produced by Yaoqiang:
//
} else {
if (minX == null || x < minX) {
minX = x;
}
if (minY == null || y < minY) {
minY = y;
}
if (maxX == null || maxX < (x + width)) {
maxX = (x + width);
}
if (maxY == null || maxY < (y + height)) {
maxY = (y + height);
}
}
}
// Edge bend points
NodeList waypoints = bpmnModel.getElementsByTagNameNS(BpmnParser.OMG_DI_NS, "waypoint");
for (int i = 0; i < waypoints.getLength(); i++) {
Element waypoint = (Element) waypoints.item(i);
Double x = Double.valueOf(waypoint.getAttribute("x"));
Double y = Double.valueOf(waypoint.getAttribute("y"));
if (minX == null || x < minX) {
minX = x;
}
if (minY == null || y < minY) {
minY = y;
}
if (maxX == null || maxX < x) {
maxX = x;
}
if (maxY == null || maxY < y) {
maxY = y;
}
}
DiagramNode diagramBounds = new DiagramNode("BPMNDiagram");
diagramBounds.setX(minX);
diagramBounds.setY(minY);
diagramBounds.setWidth(maxX - minX);
diagramBounds.setHeight(maxY - minY);
return diagramBounds;
}
protected DiagramNode getDiagramBoundsFromImage(InputStream imageStream) {
return getDiagramBoundsFromImage(imageStream, 0, 0);
}
protected DiagramNode getDiagramBoundsFromImage(InputStream imageStream, int offsetTop, int offsetBottom) {
BufferedImage image;
try {
image = ImageIO.read(imageStream);
} catch (IOException e) {
throw new ActivitiException("Error while reading process diagram image.", e);
}
DiagramNode diagramBoundsImage = getDiagramBoundsFromImage(image, offsetTop, offsetBottom);
return diagramBoundsImage;
}
protected DiagramNode getDiagramBoundsFromImage(BufferedImage image, int offsetTop, int offsetBottom) {
int width = image.getWidth();
int height = image.getHeight();
Map rowIsWhite = new TreeMap();
Map columnIsWhite = new TreeMap();
for (int row = 0; row < height; row++) {
if (!rowIsWhite.containsKey(row)) {
rowIsWhite.put(row, true);
}
if (row <= offsetTop || row > image.getHeight() - offsetBottom) {
rowIsWhite.put(row, true);
} else {
for (int column = 0; column < width; column++) {
if (!columnIsWhite.containsKey(column)) {
columnIsWhite.put(column, true);
}
int pixel = image.getRGB(column, row);
int alpha = (pixel >> 24) & 0xff;
int red = (pixel >> 16) & 0xff;
int green = (pixel >> 8) & 0xff;
int blue = (pixel >> 0) & 0xff;
if (!(alpha == 0 || (red >= GREY_THRESHOLD && green >= GREY_THRESHOLD && blue >= GREY_THRESHOLD))) {
rowIsWhite.put(row, false);
columnIsWhite.put(column, false);
}
}
}
}
int marginTop = 0;
for (int row = 0; row < height; row++) {
if (rowIsWhite.get(row)) {
++marginTop;
} else {
// Margin Top Found
break;
}
}
int marginLeft = 0;
for (int column = 0; column < width; column++) {
if (columnIsWhite.get(column)) {
++marginLeft;
} else {
// Margin Left Found
break;
}
}
int marginRight = 0;
for (int column = width - 1; column >= 0; column--) {
if (columnIsWhite.get(column)) {
++marginRight;
} else {
// Margin Right Found
break;
}
}
int marginBottom = 0;
for (int row = height -1; row >= 0; row--) {
if (rowIsWhite.get(row)) {
++marginBottom;
} else {
// Margin Bottom Found
break;
}
}
DiagramNode diagramBoundsImage = new DiagramNode();
diagramBoundsImage.setX((double) marginLeft);
diagramBoundsImage.setY((double) marginTop);
diagramBoundsImage.setWidth((double) (width - marginRight - marginLeft));
diagramBoundsImage.setHeight((double) (height - marginBottom - marginTop));
return diagramBoundsImage;
}
protected Map getElementBoundsFromBpmnDi(Document bpmnModel) {
Map listOfBounds = new HashMap();
// iterate over all DI shapes
NodeList shapes = bpmnModel.getElementsByTagNameNS(BpmnParser.BPMN_DI_NS, "BPMNShape");
for (int i = 0; i < shapes.getLength(); i++) {
Element shape = (Element) shapes.item(i);
String bpmnElementId = shape.getAttribute("bpmnElement");
// get bounds of shape
NodeList childNodes = shape.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
Node childNode = childNodes.item(j);
if (childNode instanceof Element
&& BpmnParser.BPMN_DC_NS.equals(childNode.getNamespaceURI())
&& "Bounds".equals(childNode.getLocalName())) {
DiagramNode bounds = parseBounds((Element) childNode);
bounds.setId(bpmnElementId);
listOfBounds.put(bpmnElementId, bounds);
break;
}
}
}
return listOfBounds;
}
protected DiagramNode parseBounds(Element boundsElement) {
DiagramNode bounds = new DiagramNode();
bounds.setX(Double.valueOf(boundsElement.getAttribute("x")));
bounds.setY(Double.valueOf(boundsElement.getAttribute("y")));
bounds.setWidth(Double.valueOf(boundsElement.getAttribute("width")));
bounds.setHeight(Double.valueOf(boundsElement.getAttribute("height")));
return bounds;
}
protected Map transformBoundsForImage(DiagramNode diagramBoundsImage, DiagramNode diagramBoundsXml, Map listOfBounds) {
Map listOfBoundsForImage = new HashMap();
for (Entry bounds : listOfBounds.entrySet()) {
listOfBoundsForImage.put(bounds.getKey(), transformBoundsForImage(diagramBoundsImage, diagramBoundsXml, bounds.getValue()));
}
return listOfBoundsForImage;
}
protected DiagramNode transformBoundsForImage(DiagramNode diagramBoundsImage, DiagramNode diagramBoundsXml, DiagramNode elementBounds) {
double scalingFactorX = diagramBoundsImage.getWidth() / diagramBoundsXml.getWidth();
double scalingFactorY = diagramBoundsImage.getWidth() / diagramBoundsXml.getWidth();
DiagramNode elementBoundsForImage = new DiagramNode(elementBounds.getId());
elementBoundsForImage.setX((double) Math.round((elementBounds.getX() - diagramBoundsXml.getX()) * scalingFactorX + diagramBoundsImage.getX()));
elementBoundsForImage.setY((double) Math.round((elementBounds.getY() - diagramBoundsXml.getY()) * scalingFactorY + diagramBoundsImage.getY()));
elementBoundsForImage.setWidth((double) Math.round(elementBounds.getWidth() * scalingFactorX));
elementBoundsForImage.setHeight((double) Math.round(elementBounds.getHeight() * scalingFactorY));
return elementBoundsForImage;
}
protected Map fixFlowNodePositionsIfModelFromAdonis(Document bpmnModel, Map elementBoundsFromBpmnDi) {
if (isExportedFromAdonis50(bpmnModel)) {
Map mapOfFixedBounds = new HashMap();
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
xPath.setNamespaceContext(new Bpmn20NamespaceContext());
for (Entry entry : elementBoundsFromBpmnDi.entrySet()) {
String elementId = entry.getKey();
DiagramNode elementBounds = entry.getValue();
String expression = "local-name(//bpmn:*[@id = '" + elementId + "'])";
try {
XPathExpression xPathExpression = xPath.compile(expression);
String elementLocalName = xPathExpression.evaluate(bpmnModel);
if (!"participant".equals(elementLocalName)
&& !"lane".equals(elementLocalName)
&& !"textAnnotation".equals(elementLocalName)
&& !"group".equals(elementLocalName)) {
elementBounds.setX(elementBounds.getX() - elementBounds.getWidth()/2);
elementBounds.setY(elementBounds.getY() - elementBounds.getHeight()/2);
}
} catch (XPathExpressionException e) {
throw new ActivitiException("Error while evaluating the following XPath expression on a BPMN XML document: '" + expression + "'.", e);
}
mapOfFixedBounds.put(elementId, elementBounds);
}
return mapOfFixedBounds;
} else {
return elementBoundsFromBpmnDi;
}
}
protected boolean isExportedFromAdonis50(Document bpmnModel) {
return "ADONIS".equals(bpmnModel.getDocumentElement().getAttribute("exporter"))
&& "5.0".equals(bpmnModel.getDocumentElement().getAttribute("exporterVersion"));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy