package com.mossle.bpm.util; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Ellipse2D.Double; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.imageio.ImageIO; import com.mossle.bpm.graph.ActivitiHistoryGraphBuilder; import com.mossle.bpm.graph.Edge; import com.mossle.bpm.graph.Graph; import org.activiti.bpmn.constants.BpmnXMLConstants; import org.activiti.bpmn.model.Artifact; import org.activiti.bpmn.model.BpmnModel; import org.activiti.bpmn.model.FlowElement; import org.activiti.bpmn.model.FlowElementsContainer; import org.activiti.bpmn.model.FlowNode; import org.activiti.bpmn.model.GraphicInfo; import org.activiti.bpmn.model.Lane; import org.activiti.bpmn.model.Pool; import org.activiti.bpmn.model.SequenceFlow; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.HistoricActivityInstanceQueryImpl; import org.activiti.engine.impl.Page; import org.activiti.engine.impl.cmd.GetBpmnModelCmd; import org.activiti.engine.impl.cmd.GetDeploymentProcessDefinitionCmd; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.apache.commons.io.FilenameUtils; /** * 流程图绘制工具 */ public class CustomProcessDiagramGenerator { public static final int OFFSET_SUBPROCESS = 5; public static final int OFFSET_TASK = 10; private static List<String> taskType = new ArrayList<String>(); private static List<String> eventType = new ArrayList<String>(); private static List<String> gatewayType = new ArrayList<String>(); private static List<String> subProcessType = new ArrayList<String>(); private static Color RUNNING_COLOR = Color.RED; private static Color HISTORY_COLOR = Color.decode("#337ab7"); private static Color SKIP_COLOR = Color.GRAY; private static Stroke THICK_BORDER_STROKE = new BasicStroke(3.0f); private int minX = 0; private int minY = 0; public CustomProcessDiagramGenerator() { init(); } protected static void init() { taskType.add(BpmnXMLConstants.ELEMENT_TASK_MANUAL); taskType.add(BpmnXMLConstants.ELEMENT_TASK_RECEIVE); taskType.add(BpmnXMLConstants.ELEMENT_TASK_SCRIPT); taskType.add(BpmnXMLConstants.ELEMENT_TASK_SEND); taskType.add(BpmnXMLConstants.ELEMENT_TASK_SERVICE); taskType.add(BpmnXMLConstants.ELEMENT_TASK_USER); gatewayType.add(BpmnXMLConstants.ELEMENT_GATEWAY_EXCLUSIVE); gatewayType.add(BpmnXMLConstants.ELEMENT_GATEWAY_INCLUSIVE); gatewayType.add(BpmnXMLConstants.ELEMENT_GATEWAY_EVENT); gatewayType.add(BpmnXMLConstants.ELEMENT_GATEWAY_PARALLEL); eventType.add("intermediateTimer"); eventType.add("intermediateMessageCatch"); eventType.add("intermediateSignalCatch"); eventType.add("intermediateSignalThrow"); eventType.add("messageStartEvent"); eventType.add("startTimerEvent"); eventType.add(BpmnXMLConstants.ELEMENT_ERROR); eventType.add(BpmnXMLConstants.ELEMENT_EVENT_START); eventType.add("errorEndEvent"); eventType.add(BpmnXMLConstants.ELEMENT_EVENT_END); subProcessType.add(BpmnXMLConstants.ELEMENT_SUBPROCESS); subProcessType.add(BpmnXMLConstants.ELEMENT_CALL_ACTIVITY); } public InputStream generateDiagram(String processInstanceId) throws IOException { HistoricProcessInstance historicProcessInstance = Context .getCommandContext().getHistoricProcessInstanceEntityManager() .findHistoricProcessInstance(processInstanceId); String processDefinitionId = historicProcessInstance .getProcessDefinitionId(); GetBpmnModelCmd getBpmnModelCmd = new GetBpmnModelCmd( processDefinitionId); BpmnModel bpmnModel = getBpmnModelCmd.execute(Context .getCommandContext()); // Point point = getMinXAndMinY(bpmnModel); // this.minX = point.x; // this.minY = point.y; // this.minX = (this.minX <= 5) ? 5 : this.minX; // this.minY = (this.minY <= 5) ? 5 : this.minY; // this.minX -= 5; // this.minY -= 5; ProcessDefinitionEntity definition = new GetDeploymentProcessDefinitionCmd( processDefinitionId).execute(Context.getCommandContext()); String diagramResourceName = definition.getDiagramResourceName(); String deploymentId = definition.getDeploymentId(); byte[] bytes = Context .getCommandContext() .getResourceEntityManager() .findResourceByDeploymentIdAndResourceName(deploymentId, diagramResourceName).getBytes(); InputStream originDiagram = new ByteArrayInputStream(bytes); BufferedImage image = ImageIO.read(originDiagram); HistoricActivityInstanceQueryImpl historicActivityInstanceQueryImpl = new HistoricActivityInstanceQueryImpl(); historicActivityInstanceQueryImpl.processInstanceId(processInstanceId) .orderByHistoricActivityInstanceStartTime().asc(); Page page = new Page(0, 100); List<HistoricActivityInstance> activityInstances = Context .getCommandContext() .getHistoricActivityInstanceEntityManager() .findHistoricActivityInstancesByQueryCriteria( historicActivityInstanceQueryImpl, page); this.drawHistoryFlow(image, processInstanceId); for (HistoricActivityInstance historicActivityInstance : activityInstances) { String historicActivityId = historicActivityInstance .getActivityId(); ActivityImpl activity = definition.findActivity(historicActivityId); if (activity != null) { if (historicActivityInstance.getEndTime() == null) { // 节点正在运行中 signRunningNode(image, activity.getX() - this.minX, activity.getY() - this.minY, activity.getWidth(), activity.getHeight(), historicActivityInstance.getActivityType()); } else { String deleteReason = null; if (historicActivityInstance.getTaskId() != null) { deleteReason = Context .getCommandContext() .getHistoricTaskInstanceEntityManager() .findHistoricTaskInstanceById( historicActivityInstance.getTaskId()) .getDeleteReason(); } // 节点已经结束 if ("跳过".equals(deleteReason)) { signSkipNode(image, activity.getX() - this.minX, activity.getY() - this.minY, activity.getWidth(), activity.getHeight(), historicActivityInstance.getActivityType()); } else { signHistoryNode(image, activity.getX() - this.minX, activity.getY() - this.minY, activity.getWidth(), activity.getHeight(), historicActivityInstance.getActivityType()); } } } } ByteArrayOutputStream out = new ByteArrayOutputStream(); String formatName = getDiagramExtension(diagramResourceName); ImageIO.write(image, formatName, out); return new ByteArrayInputStream(out.toByteArray()); } private static String getDiagramExtension(String diagramResourceName) { return FilenameUtils.getExtension(diagramResourceName); } /** * 标记运行节点 * * @param image * 原始图片 * @param x * 左上角节点坐在X位置 * @param y * 左上角节点坐在Y位置 * @param width * 宽 * @param height * 高 * @param activityType * 节点类型 */ private static void signRunningNode(BufferedImage image, int x, int y, int width, int height, String activityType) { Color nodeColor = RUNNING_COLOR; Graphics2D graphics = image.createGraphics(); try { drawNodeBorder(x, y, width, height, graphics, nodeColor, activityType); } finally { graphics.dispose(); } } /** * 标记历史节点 * * @param image * 原始图片 * @param x * 左上角节点坐在X位置 * @param y * 左上角节点坐在Y位置 * @param width * 宽 * @param height * 高 * @param activityType * 节点类型 */ private static void signHistoryNode(BufferedImage image, int x, int y, int width, int height, String activityType) { Color nodeColor = HISTORY_COLOR; Graphics2D graphics = image.createGraphics(); try { drawNodeBorder(x, y, width, height, graphics, nodeColor, activityType); } finally { graphics.dispose(); } } private static void signSkipNode(BufferedImage image, int x, int y, int width, int height, String activityType) { Color nodeColor = SKIP_COLOR; Graphics2D graphics = image.createGraphics(); try { drawNodeBorder(x, y, width, height, graphics, nodeColor, activityType); } finally { graphics.dispose(); } } /** * 绘制节点边框 * * @param x * 左上角节点坐在X位置 * @param y * 左上角节点坐在Y位置 * @param width * 宽 * @param height * 高 * @param graphics * 绘图对象 * @param color * 节点边框颜色 * @param activityType * 节点类型 */ protected static void drawNodeBorder(int x, int y, int width, int height, Graphics2D graphics, Color color, String activityType) { graphics.setPaint(color); graphics.setStroke(THICK_BORDER_STROKE); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (taskType.contains(activityType)) { drawTask(x, y, width, height, graphics); } else if (gatewayType.contains(activityType)) { drawGateway(x, y, width, height, graphics); } else if (eventType.contains(activityType)) { drawEvent(x, y, width, height, graphics); } else if (subProcessType.contains(activityType)) { drawSubProcess(x, y, width, height, graphics); } } /** * 绘制任务 */ protected static void drawTask(int x, int y, int width, int height, Graphics2D graphics) { RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, OFFSET_TASK, OFFSET_TASK); graphics.draw(rect); } /** * 绘制网关 */ protected static void drawGateway(int x, int y, int width, int height, Graphics2D graphics) { Polygon rhombus = new Polygon(); rhombus.addPoint(x, y + (height / 2)); rhombus.addPoint(x + (width / 2), y + height); rhombus.addPoint(x + width, y + (height / 2)); rhombus.addPoint(x + (width / 2), y); graphics.draw(rhombus); } /** * 绘制任务 */ protected static void drawEvent(int x, int y, int width, int height, Graphics2D graphics) { Double circle = new Ellipse2D.Double(x, y, width, height); graphics.draw(circle); } /** * 绘制子流程 */ protected static void drawSubProcess(int x, int y, int width, int height, Graphics2D graphics) { RoundRectangle2D rect = new RoundRectangle2D.Double(x + 1, y + 1, width - 2, height - 2, OFFSET_SUBPROCESS, OFFSET_SUBPROCESS); graphics.draw(rect); } protected Point getMinXAndMinY(BpmnModel bpmnModel) { // We need to calculate maximum values to know how big the image will be in its entirety double theMinX = java.lang.Double.MAX_VALUE; double theMaxX = 0; double theMinY = java.lang.Double.MAX_VALUE; double theMaxY = 0; for (Pool pool : bpmnModel.getPools()) { GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId()); theMinX = graphicInfo.getX(); theMaxX = graphicInfo.getX() + graphicInfo.getWidth(); theMinY = graphicInfo.getY(); theMaxY = graphicInfo.getY() + graphicInfo.getHeight(); } List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel); for (FlowNode flowNode : flowNodes) { GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode .getId()); // width if ((flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth()) > theMaxX) { theMaxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth(); } if (flowNodeGraphicInfo.getX() < theMinX) { theMinX = flowNodeGraphicInfo.getX(); } // height if ((flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight()) > theMaxY) { theMaxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight(); } if (flowNodeGraphicInfo.getY() < theMinY) { theMinY = flowNodeGraphicInfo.getY(); } for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { List<GraphicInfo> graphicInfoList = bpmnModel .getFlowLocationGraphicInfo(sequenceFlow.getId()); for (GraphicInfo graphicInfo : graphicInfoList) { // width if (graphicInfo.getX() > theMaxX) { theMaxX = graphicInfo.getX(); } if (graphicInfo.getX() < theMinX) { theMinX = graphicInfo.getX(); } // height if (graphicInfo.getY() > theMaxY) { theMaxY = graphicInfo.getY(); } if (graphicInfo.getY() < theMinY) { theMinY = graphicInfo.getY(); } } } } List<Artifact> artifacts = gatherAllArtifacts(bpmnModel); for (Artifact artifact : artifacts) { GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact .getId()); if (artifactGraphicInfo != null) { // width if ((artifactGraphicInfo.getX() + artifactGraphicInfo .getWidth()) > theMaxX) { theMaxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth(); } if (artifactGraphicInfo.getX() < theMinX) { theMinX = artifactGraphicInfo.getX(); } // height if ((artifactGraphicInfo.getY() + artifactGraphicInfo .getHeight()) > theMaxY) { theMaxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight(); } if (artifactGraphicInfo.getY() < theMinY) { theMinY = artifactGraphicInfo.getY(); } } List<GraphicInfo> graphicInfoList = bpmnModel .getFlowLocationGraphicInfo(artifact.getId()); if (graphicInfoList != null) { for (GraphicInfo graphicInfo : graphicInfoList) { // width if (graphicInfo.getX() > theMaxX) { theMaxX = graphicInfo.getX(); } if (graphicInfo.getX() < theMinX) { theMinX = graphicInfo.getX(); } // height if (graphicInfo.getY() > theMaxY) { theMaxY = graphicInfo.getY(); } if (graphicInfo.getY() < theMinY) { theMinY = graphicInfo.getY(); } } } } int nrOfLanes = 0; for (org.activiti.bpmn.model.Process process : bpmnModel.getProcesses()) { for (Lane l : process.getLanes()) { nrOfLanes++; GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId()); // // width if ((graphicInfo.getX() + graphicInfo.getWidth()) > theMaxX) { theMaxX = graphicInfo.getX() + graphicInfo.getWidth(); } if (graphicInfo.getX() < theMinX) { theMinX = graphicInfo.getX(); } // height if ((graphicInfo.getY() + graphicInfo.getHeight()) > theMaxY) { theMaxY = graphicInfo.getY() + graphicInfo.getHeight(); } if (graphicInfo.getY() < theMinY) { theMinY = graphicInfo.getY(); } } } // Special case, see http://jira.codehaus.org/browse/ACT-1431 if ((flowNodes.size() == 0) && (bpmnModel.getPools().size() == 0) && (nrOfLanes == 0)) { // Nothing to show theMinX = 0; theMinY = 0; } return new Point((int) theMinX, (int) theMinY); } protected static List<Artifact> gatherAllArtifacts(BpmnModel bpmnModel) { List<Artifact> artifacts = new ArrayList<Artifact>(); for (org.activiti.bpmn.model.Process process : bpmnModel.getProcesses()) { artifacts.addAll(process.getArtifacts()); } return artifacts; } protected static List<FlowNode> gatherAllFlowNodes(BpmnModel bpmnModel) { List<FlowNode> flowNodes = new ArrayList<FlowNode>(); for (org.activiti.bpmn.model.Process process : bpmnModel.getProcesses()) { flowNodes.addAll(gatherAllFlowNodes(process)); } return flowNodes; } protected static List<FlowNode> gatherAllFlowNodes( FlowElementsContainer flowElementsContainer) { List<FlowNode> flowNodes = new ArrayList<FlowNode>(); for (FlowElement flowElement : flowElementsContainer.getFlowElements()) { if (flowElement instanceof FlowNode) { flowNodes.add((FlowNode) flowElement); } if (flowElement instanceof FlowElementsContainer) { flowNodes .addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement)); } } return flowNodes; } public void drawHistoryFlow(BufferedImage image, String processInstanceId) { HistoricProcessInstance historicProcessInstance = Context .getCommandContext().getHistoricProcessInstanceEntityManager() .findHistoricProcessInstance(processInstanceId); String processDefinitionId = historicProcessInstance .getProcessDefinitionId(); Graph graph = new ActivitiHistoryGraphBuilder(processInstanceId) .build(); for (Edge edge : graph.getEdges()) { drawSequenceFlow(image, processDefinitionId, edge.getName()); } } public void drawSequenceFlow(BufferedImage image, String processDefinitionId, String sequenceFlowId) { GetBpmnModelCmd getBpmnModelCmd = new GetBpmnModelCmd( processDefinitionId); BpmnModel bpmnModel = getBpmnModelCmd.execute(Context .getCommandContext()); Graphics2D graphics = image.createGraphics(); graphics.setPaint(HISTORY_COLOR); graphics.setStroke(new BasicStroke(2f)); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); try { List<GraphicInfo> graphicInfoList = bpmnModel .getFlowLocationGraphicInfo(sequenceFlowId); int[] xPoints = new int[graphicInfoList.size()]; int[] yPoints = new int[graphicInfoList.size()]; for (int i = 1; i < graphicInfoList.size(); i++) { GraphicInfo graphicInfo = graphicInfoList.get(i); GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); if (i == 1) { xPoints[0] = (int) previousGraphicInfo.getX() - minX; yPoints[0] = (int) previousGraphicInfo.getY() - minY; } xPoints[i] = (int) graphicInfo.getX() - minX; yPoints[i] = (int) graphicInfo.getY() - minY; } int radius = 15; Path2D path = new Path2D.Double(); for (int i = 0; i < xPoints.length; i++) { Integer anchorX = xPoints[i]; Integer anchorY = yPoints[i]; double targetX = anchorX; double targetY = anchorY; double ax = 0; double ay = 0; double bx = 0; double by = 0; double zx = 0; double zy = 0; if ((i > 0) && (i < (xPoints.length - 1))) { Integer cx = anchorX; Integer cy = anchorY; // pivot point of prev line double lineLengthY = yPoints[i] - yPoints[i - 1]; // pivot point of prev line double lineLengthX = xPoints[i] - xPoints[i - 1]; double lineLength = Math.sqrt(Math.pow(lineLengthY, 2) + Math.pow(lineLengthX, 2)); double dx = (lineLengthX * radius) / lineLength; double dy = (lineLengthY * radius) / lineLength; targetX = targetX - dx; targetY = targetY - dy; // isDefaultConditionAvailable = isDefault && i == 1 && lineLength > 10; if ((lineLength < (2 * radius)) && (i > 1)) { targetX = xPoints[i] - (lineLengthX / 2); targetY = yPoints[i] - (lineLengthY / 2); } // pivot point of next line lineLengthY = yPoints[i + 1] - yPoints[i]; lineLengthX = xPoints[i + 1] - xPoints[i]; lineLength = Math.sqrt(Math.pow(lineLengthY, 2) + Math.pow(lineLengthX, 2)); if (lineLength < radius) { lineLength = radius; } dx = (lineLengthX * radius) / lineLength; dy = (lineLengthY * radius) / lineLength; double nextSrcX = xPoints[i] + dx; double nextSrcY = yPoints[i] + dy; if ((lineLength < (2 * radius)) && (i < (xPoints.length - 2))) { nextSrcX = xPoints[i] + (lineLengthX / 2); nextSrcY = yPoints[i] + (lineLengthY / 2); } double dx0 = (cx - targetX) / 3; double dy0 = (cy - targetY) / 3; ax = cx - dx0; ay = cy - dy0; double dx1 = (cx - nextSrcX) / 3; double dy1 = (cy - nextSrcY) / 3; bx = cx - dx1; by = cy - dy1; zx = nextSrcX; zy = nextSrcY; } if (i == 0) { path.moveTo(targetX, targetY); } else { path.lineTo(targetX, targetY); } if ((i > 0) && (i < (xPoints.length - 1))) { // add curve path.curveTo(ax, ay, bx, by, zx, zy); } } graphics.draw(path); // draw arrow Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]); int ARROW_WIDTH = 5; int doubleArrowWidth = 2 * ARROW_WIDTH; Polygon arrowHead = new Polygon(); arrowHead.addPoint(0, 0); arrowHead.addPoint(-ARROW_WIDTH, -doubleArrowWidth); arrowHead.addPoint(ARROW_WIDTH, -doubleArrowWidth); AffineTransform transformation = new AffineTransform(); transformation.setToIdentity(); double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); transformation.translate(line.x2, line.y2); transformation.rotate((angle - (Math.PI / 2d))); AffineTransform originalTransformation = graphics.getTransform(); graphics.setTransform(transformation); graphics.fill(arrowHead); graphics.setTransform(originalTransformation); } finally { graphics.dispose(); } } }