一、引入jar包

 <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M1</version>
        </dependency>

        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-image-generator</artifactId>
            <version>7.1.0.M1</version>
        </dependency>

二、配置文件

#  activity
  activiti:
    #1.false,默认值,acticiti启动时对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
    #2.true,acticiti会对数据中所有的表进行更新操作,如果表不存在,则自动创建
    #3.create_drop,在acticiti启动时创建表,关闭时删除表(必须手动关闭引擎才能删除表)
    #4.drop_create,在acticiti启动时删除原来的表,然后创建新表(不需要手动关闭引擎)
    database-schema-update: true
    #默认true,效验流程文件,默认效验resources下的processes文件夹里的流程,为true自动部署流程,为false则不部署
    check-process-definitions: true
    # 流程文件存放目录
    process-definition-location-prefix: classpath:/processes/
    async-executor-activate: false
    # 启用历史记录
    db-history-used: true
    #历史记录等级
    #1.none:不保存任何历史记录,因此在流程执行过程中,这是最高效的
    #2.acticiti:级别高于none,保存流程实例与流程行为,其他数据不保存
    #3.audit:除activiti级别会保存的数据外,还会保存全部的流程任务及其属性,audit为默认值
    #4.full:保存历史数据的最高级别,除了保存audit级别的数据外,还会保存其他流程相关的细节数据,包括一些流程参数等
    history-level: full

  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,
      org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration


注:通过上面配置文件启动项目即生成activiti相关表

在这里插入图片描述
数据库表

act_hi_*:'hi’表示 history,此前缀的表包含历史数据,如历史(结束)流程实例,变量,任务等等。

act_ge_*:'ge’表示 general,此前缀的表为通用数据,用于不同场景中。

act_evt_*:'evt’表示 event,此前缀的表为事件日志。

act_procdef_*:'procdef’表示 processdefine,此前缀的表为记录流程定义信息。

act_re_*:'re’表示 repository,此前缀的表包含了流程定义和流程静态资源(图片,规则等等)。

act_ru_*:'ru’表示 runtime,此前缀的表是记录运行时的数据,包含流程实例,任务,变量,异步任务等运行中的数据。Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。

表名表注释
act_ge_bytearray二进制数据表,存储通用的流程定义和流程资源。
act_ge_bytearray二进制数据表,存储通用的流程定义和流程资源。
act_ge_property系统相关属性,属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录。
act_re_deployment部署信息表
act_re_model流程设计模型部署表
act_re_procdef流程定义数据表
act_ru_deadletter_job作业死亡信息表,作业失败超过重试次数
act_ru_event_subscr运行时事件表
act_ru_execution运行时流程执行实例表
act_ru_identitylink运行时用户信息表
act_ru_integration运行时积分表
act_ru_job运行时作业信息表
act_ru_suspended_job运行时作业暂停表
act_ru_task运行时任务信息表
act_ru_timer_job运行时定时器作业表
act_ru_variable运行时变量信息表
act_hi_actinst历史节点表
act_hi_attachment历史附件表
act_hi_comment历史意见表
act_hi_detail历史详情表,提供历史变量的查询
act_hi_identitylink历史流程用户信息表
act_hi_procinst历史流程实例表
act_hi_taskinst历史任务实例表
act_hi_varinst历史变量表
act_evt_log流程引擎的通用事件日志记录表
act_procdef_info流程定义的动态变更信息

BPM节点介绍
  根据BPMN2.0规范的分类划分为以下部分:
  1.启动与结束事件(event)
  2.顺序流(Sequence Flow)
  3.任务(Task)
  4.网关(Gateway)
  5.子流程(Subprocess)
  6.边界事件(Boundary Event)
  7.中间事件(Intermediate Event)
  8.监听器(Listener)

三、下载画流程图插件

可参考此文章https://blog.csdn.net/weixin_43025151/article/details/125967784

四、流程图绘制

流程节点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

网关:这里只以普通网关为例子

在这里插入图片描述

绘制完成

在这里插入图片描述

五、流程节点描述

一人代办:当前节点为只有一个人处理的时候是在Assignee中给定参数

在这里插入图片描述

多人待受理:当前节点为选择多个人接收但只需其中一个人签收代办即可是在Candidate Users中给定参数

在这里插入图片描述

会签:当前节点需要多个人全部处理后节点才往下走

在这里插入图片描述
单击当前节点点击箭头所指的工具图标选择以下图标即可将当前节点变成会签节点
在这里插入图片描述

参数配置
在这里插入图片描述
注:

  1. nrOfCompletedInstances 当前节点完成人数
  2. nrOfInstances 节点完成需要的中人数
  3. nrOfCompletedInstances/nrOfInstances 当前节点完成总人数除以需要完成任务的总人数的结果为1时,流程就可以走向网关节点

在这里插入图片描述
在这里插入图片描述
注:需要配置监听器来判断会签节点人员是否已审批完或者退回

最后保存文件为test.bpmn20.xml,test为文件名前缀(可随意命名,bpmn20.xml不能改变),将该文件保存之resources的的processes文件夹下
在这里插入图片描述

六、流程启动

直接上代码


import com.gxkj.common.modular.activiti.config.JumpAnyWhereCmd;
import com.gxkj.common.modular.activiti.service.BpmService;
import com.gxkj.common.modular.activiti.service.ProcessImageService;
import com.gxkj.common.modular.htgl.model.TaskVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import jodd.util.StringUtil;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.util.json.JSONObject;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

@Api
@RestController
@RequestMapping("/bpm")
public class BpmController {
    private final static Logger Log = LoggerFactory.getLogger(BpmController.class);
    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private ProcessImageService processImageService;
    @Autowired
    private BpmService bpmService;


    @ApiOperation(value = "部署请假工作流", httpMethod = "POST")
    @RequestMapping(value = "/deploy")
    public String deploy(@RequestBody Map<String,Object> map) {
        // 创建一个部署对象
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deployment=repositoryService.createDeployment()
                .name(map.get("name").toString())
                .disableSchemaValidation()
                .addClasspathResource(map.get("bpmn").toString())
                .deploy();
        String id = deployment.getId();
        return "部署成功";
    }
    //
    @ApiOperation("启动请假工作流")
    @PostMapping("/start")
    public String start(@RequestBody Map<String,Object> variables){
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //根据流程定义的key启动流程实例
        //设置流程变量
        String key = variables.get("key").toString();

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key,variables);
        //获取流程实例的相关信息
        String processDefinitionId = processInstance.getProcessDefinitionId();
        String deploymentId = processInstance.getDeploymentId();
        String id = processInstance.getId();
        String activityId = processInstance.getActivityId();

        JSONObject jsonObject=new JSONObject();
        jsonObject.put("流程定义的id" , processDefinitionId);
        jsonObject.put("流程部署的id" , deploymentId);
        jsonObject.put("流程实例" , id);
        jsonObject.put("当前活动的id" , activityId);
        String list=jsonObject.toString();
         自动过掉第一个任务
        查询第一个任务
        //TaskQuery taskQuery = taskService.createTaskQuery().active().taskAssignee(proposer).processDefinitionKey(key);
        设置流程实例id
        //taskQuery.processInstanceId(id);
        //
        //Task task = taskQuery.singleResult();
        //
        //if (task != null) {
        //    taskService.complete(task.getId());
        //    // 判断第下一个待受理节点人是否为多人,如果是一个人直接到代办任务重
        //    if (handleLead.indexOf(",") == -1) {
        //        String taskId = bpmService.getTaskIdByProId(id);
        //        processEngine.getTaskService().claim(taskId,handleLead);
        //    }
        //}

        return list;
    }

    @RequestMapping(value = "/getFlowImgByInstanceId")
    @ApiOperation(value = "获取流程实例跟踪", httpMethod = "POST" ,notes = "获取流程实例跟踪")
    public void getFlowImgByInstanceId(@RequestBody Map<String, Object> map, HttpServletResponse response) {

        processImageService.getFlowImgByInstanceId(map.get("processInstanceId").toString(), response);
    }

    @RequestMapping(value = "/implement")
    @ApiOperation(value = "执行任务(会签任务)", httpMethod = "POST" ,notes = "执行任务")
    public String implement(@RequestBody Map<String, Object> map){
        TaskService taskService = processEngine.getTaskService();
       
        taskService.complete(map.get("taskId").toString(),map);
        return "执行成功";
    }

    @RequestMapping(value = "/implementCountersign")
    @ApiOperation(value = "执行任务(会签任务)", httpMethod = "POST" ,notes = "执行任务")
    public String implementCountersign(@RequestBody Map<String, Object> map){
        TaskService taskService = processEngine.getTaskService();
        Map<String, Object> map2 = new HashMap<>();
        List<String> leaderList = new ArrayList<>();
        leaderList.add("zhangsan");
        leaderList.add("lisi");
        leaderList.add("wangwu");

        map2.put("countersignList", leaderList);
        taskService.complete(map.get("taskId").toString(),map2);
        return "执行成功";
    }

    @RequestMapping(value = "/countersign")
    @ApiOperation(value = "会签", httpMethod = "POST" ,notes = "会签")
    public String countersign(@RequestBody Map<String, Object> map){
        TaskService taskService = processEngine.getTaskService();
        boolean pass = (boolean)map.get("pass");
        if (!pass) {
         // 退回
            String processInstanceId = map.get("processId").toString();
            String taskId = map.get("taskId").toString();
            if (StringUtil.isNotBlank(processInstanceId)) {
                // 将所有未完成的节点查找出来
                List<TaskVo> taskList = bpmService.getTaskList(processInstanceId,taskId);
              // 查询sql
              //                 select id_ as taskId from act_ru_task where PROC_INST_ID_ = #{processId} and id_ !=#{id}

				  if (!taskList.isEmpty()) {
                    // 先将除了本身以外的节点先过滤掉
                    Map<String, Object> dataMap = new HashMap<>();
                    dataMap.put("pass", true);
                    for (TaskVo taskVo : taskList) {
                        taskService.complete(taskVo.getTaskId(),dataMap);

                    }
                }
            }
        }
        taskService.complete(map.get("taskId").toString(),map);
        return "执行成功";
    }


    @ApiOperation(value = "受理任务", httpMethod = "POST")
    @RequestMapping(value = "/acceptTask")
    public String acceptTask(@RequestBody Map<String,Object> map) {
        String taskId = map.get("taskId").toString();
        String userName = map.get("userName").toString();
        String taskName = map.get("taskName").toString();
        // 创建一个部署对象
        Task task = taskService.createTaskQuery()
                .taskId(taskId)
                .taskName(taskName)
                .singleResult();
        if (task != null) {
            // 受理任务
            taskService.claim(taskId, userName);
        }
        return "受理任务成功";
    }

    @RequestMapping(value = "/rollback")
    @ApiOperation(value = "回退任务", httpMethod = "POST" ,notes = "回退任务")
    public String rollback(@RequestBody Map<String, Object> map){
        processEngine.getManagementService().executeCommand(new JumpAnyWhereCmd(map.get("taskId").toString(),map.get("taskName").toString()));

        return "执行成功";
    }
}

相应的postman请求

发布流程图
在这里插入图片描述

启动流程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

执行任务
在这里插入图片描述
taskId为act_ru_task表中的id

受理任务

在这里插入图片描述

执行任务(会签)
在这里插入图片描述
会签所需要的的人已在代码中实现,当然也能在这里输入(懒得写。。)

会签
在这里插入图片描述

这里主要把监听器中的代码贴出,主要的执行代码已在上面有所编写

import com.gxkj.common.modular.activiti.service.BpmService;
import com.gxkj.common.modular.activiti.util.BpmUtil;
import com.gxkj.common.modular.htgl.model.TaskVo;
import jodd.util.StringUtil;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.task.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;


import java.util.List;
import java.util.Objects;

public class SignListener implements TaskListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(SignListener.class);

    @Override
    public void notify(DelegateTask delegateTask) {
        /**
         *
         */
            //获取流程id
            String taskId = delegateTask.getExecutionId();
            //获取流程参数pass,会签人员完成自己的审批任务时会添加流程参数pass,false为拒绝,true为同意
            RuntimeService runtimeService = BpmUtil.getObject(RuntimeService.class);
            boolean pass = (Boolean) runtimeService.getVariable(taskId, "pass");

        /*  ${nrOfCompletedInstances/nrOfInstances==1}
             * false:有一个人拒绝,整个流程就结束了,
             *     因为Complete condition的值为pass == false,即,当流程参数为pass时会签就结束开始下一个任务
             *     所以,当pass == false时,直接设置下一个流程跳转需要的参数
             * true:审批人同意,同时要判断是不是所有的人都已经完成了,而不是由一个人同意该会签就结束
             *     值得注意的是如果一个审批人完成了审批进入到该监听时nrOfCompletedInstances的值还没有更新,因此需要+1
             */
        Integer complete = (Integer) runtimeService.getVariable(taskId, "nrOfCompletedInstances");
        Integer all = (Integer) runtimeService.getVariable(taskId, "nrOfInstances");
       // 这里的test对应的是第一个节点中的对应的参数
        String username= (String) runtimeService.getVariable(delegateTask.getExecutionId(), "test");
            if(pass == false){
                //会签结束,返回发起人节点
                runtimeService.setVariable(taskId, "countersignState", 0);
                runtimeService.setVariable(taskId, "test", username);
            }else{

                //说明都完成了并且没有人拒绝
                if((complete + 1) / all == 1){
                // 流程流向下个节点 test2为下个节点人的参数
                    runtimeService.setVariable(taskId, "countersignState", 1);
                    runtimeService.setVariable(taskId, "test2", "xiaowang");

                }
            }
        }

}

七、待受理、代办、已办任务sql

待受理任务sql

SELECT DISTINCT
	b.* 
FROM
	act_ru_task b
	INNER JOIN act_ru_identitylink a ON b.ID_ = a.TASK_ID_
	INNER JOIN act_re_procdef p ON b.PROC_DEF_ID_ = p.ID_ 
WHERE
	b.ASSIGNEE_ IS NULL 
	AND a.USER_ID_ = 'test' 
	AND a.TYPE_ = 'candidate'

代办任务sql

SELECT
	ta.* 
FROM
	ACT_RU_TASK ta
	INNER JOIN ACT_RE_PROCDEF D ON ta.PROC_DEF_ID_ = D.ID_ 
WHERE
	ta.ASSIGNEE_ = 'test'

已办任务sql

SELECT
	ta.* 
FROM
	ACT_RU_TASK ta
	INNER JOIN ACT_HI_TASKINST SK ON mi.process_id = SK.PROC_INST_ID_
	INNER JOIN ACT_RE_PROCDEF D ON SK.PROC_DEF_ID_ = D.ID_ 
WHERE
	SK.ASSIGNEE_ = 'test' 
	AND SK.END_TIME_ IS NOT NULL

八、获取动态流程图

在这里插入图片描述

1.重写DefaultProcessDiagramGenerator类

package com.gxkj.common.modular.activiti.config;

import java.awt.*;
import java.io.InputStream;
import java.util.*;
import java.util.List;

import org.activiti.bpmn.model.Activity;
import org.activiti.bpmn.model.Artifact;
import org.activiti.bpmn.model.Association;
import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BoundaryEvent;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.BusinessRuleTask;
import org.activiti.bpmn.model.CallActivity;
import org.activiti.bpmn.model.CompensateEventDefinition;
import org.activiti.bpmn.model.EndEvent;
import org.activiti.bpmn.model.ErrorEventDefinition;
import org.activiti.bpmn.model.Event;
import org.activiti.bpmn.model.EventDefinition;
import org.activiti.bpmn.model.EventGateway;
import org.activiti.bpmn.model.EventSubProcess;
import org.activiti.bpmn.model.ExclusiveGateway;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowElementsContainer;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Gateway;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.bpmn.model.InclusiveGateway;
import org.activiti.bpmn.model.IntermediateCatchEvent;
import org.activiti.bpmn.model.Lane;
import org.activiti.bpmn.model.ManualTask;
import org.activiti.bpmn.model.MessageEventDefinition;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.bpmn.model.ParallelGateway;
import org.activiti.bpmn.model.Pool;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.ReceiveTask;
import org.activiti.bpmn.model.ScriptTask;
import org.activiti.bpmn.model.SendTask;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.ServiceTask;
import org.activiti.bpmn.model.SignalEventDefinition;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.bpmn.model.SubProcess;
import org.activiti.bpmn.model.Task;
import org.activiti.bpmn.model.TextAnnotation;
import org.activiti.bpmn.model.ThrowEvent;
import org.activiti.bpmn.model.TimerEventDefinition;
import org.activiti.bpmn.model.UserTask;
import org.activiti.bpmn.model.Transaction;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.exception.ActivitiInterchangeInfoNotFoundException;
import org.activiti.image.exception.ActivitiImageException;

/**
 1. Class to generate an svg based the diagram interchange information in a
 2. BPMN 2.0 process.
 */
public class DefaultProcessDiagramGenerator implements ProcessDiagramGenerator {

    private static final String DEFAULT_ACTIVITY_FONT_NAME = "宋体";

    private static final String DEFAULT_LABEL_FONT_NAME = "宋体";

    private static final String DEFAULT_ANNOTATION_FONT_NAME = "宋体";

    private static final String DEFAULT_DIAGRAM_IMAGE_FILE_NAME = "/image/na.svg";

    protected Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions = new HashMap<Class<? extends BaseElement>, ActivityDrawInstruction>();

    protected Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions = new HashMap<Class<? extends BaseElement>, ArtifactDrawInstruction>();

    @Override
    public String getDefaultActivityFontName() {
        return DEFAULT_ACTIVITY_FONT_NAME;
    }

    @Override
    public String getDefaultLabelFontName() {
        return DEFAULT_LABEL_FONT_NAME;
    }

    @Override
    public String getDefaultAnnotationFontName() {
        return DEFAULT_ANNOTATION_FONT_NAME;
    }

    @Override
    public String getDefaultDiagramImageFileName() {
        return DEFAULT_DIAGRAM_IMAGE_FILE_NAME;
    }

    // The instructions on how to draw a certain construct is
    // created statically and stored in a map for performance.
    public DefaultProcessDiagramGenerator() {
        // start event
        activityDrawInstructions.put(StartEvent.class,
                new  ActivityDrawInstruction() {

                    @Override
                    public void draw( DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        StartEvent startEvent = (StartEvent) flowNode;
                        if (startEvent.getEventDefinitions() != null && !startEvent.getEventDefinitions().isEmpty()) {
                            EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0);
                            if (eventDefinition instanceof TimerEventDefinition) {
                                processDiagramCanvas.drawTimerStartEvent(flowNode.getId(),
                                        graphicInfo);
                            } else if (eventDefinition instanceof ErrorEventDefinition) {
                                processDiagramCanvas.drawErrorStartEvent(flowNode.getId(),
                                        graphicInfo);
                            } else if (eventDefinition instanceof SignalEventDefinition) {
                                processDiagramCanvas.drawSignalStartEvent(flowNode.getId(),
                                        graphicInfo);
                            } else if (eventDefinition instanceof MessageEventDefinition) {
                                processDiagramCanvas.drawMessageStartEvent(flowNode.getId(),
                                        graphicInfo);
                            } else {
                                processDiagramCanvas.drawNoneStartEvent(flowNode.getId(),
                                        graphicInfo);
                            }
                        } else {
                            processDiagramCanvas.drawNoneStartEvent(flowNode.getId(),
                                    graphicInfo);
                        }
                    }
                });

        // signal catch
        activityDrawInstructions.put(IntermediateCatchEvent.class,
                new  ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowNode;
                        if (intermediateCatchEvent.getEventDefinitions() != null && !intermediateCatchEvent.getEventDefinitions()
                                .isEmpty()) {
                            if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
                                processDiagramCanvas.drawCatchingSignalEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo,
                                        true);
                            } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) {
                                processDiagramCanvas.drawCatchingTimerEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo,
                                        true);
                            } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) {
                                processDiagramCanvas.drawCatchingMessageEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo,
                                        true);
                            }
                        }
                    }
                });

        // signal throw
        activityDrawInstructions.put(ThrowEvent.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        ThrowEvent throwEvent = (ThrowEvent) flowNode;
                        if (throwEvent.getEventDefinitions() != null && !throwEvent.getEventDefinitions().isEmpty()) {
                            if (throwEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
                                processDiagramCanvas.drawThrowingSignalEvent(flowNode.getId(),
                                        graphicInfo);
                            } else if (throwEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
                                processDiagramCanvas.drawThrowingCompensateEvent(flowNode.getId(),
                                        graphicInfo);
                            } else {
                                processDiagramCanvas.drawThrowingNoneEvent(flowNode.getId(),
                                        graphicInfo);
                            }
                        } else {
                            processDiagramCanvas.drawThrowingNoneEvent(flowNode.getId(),
                                    graphicInfo);
                        }
                    }
                });

        // end event
        activityDrawInstructions.put(EndEvent.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        EndEvent endEvent = (EndEvent) flowNode;
                        if (endEvent.getEventDefinitions() != null && !endEvent.getEventDefinitions().isEmpty()) {
                            if (endEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) {
                                processDiagramCanvas.drawErrorEndEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo);
                            } else {
                                processDiagramCanvas.drawNoneEndEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo);
                            }
                        } else {
                            processDiagramCanvas.drawNoneEndEvent(flowNode.getId(),
                                    flowNode.getName(),
                                    graphicInfo);
                        }
                    }
                });

        // task
        activityDrawInstructions.put(Task.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawTask(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // user task
        activityDrawInstructions.put(UserTask.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawUserTask(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // script task
        activityDrawInstructions.put(ScriptTask.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawScriptTask(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // service task
        activityDrawInstructions.put(ServiceTask.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        ServiceTask serviceTask = (ServiceTask) flowNode;
                        processDiagramCanvas.drawServiceTask(flowNode.getId(),
                                serviceTask.getName(),
                                graphicInfo);
                    }
                });

        // receive task
        activityDrawInstructions.put(ReceiveTask.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawReceiveTask(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // send task
        activityDrawInstructions.put(SendTask.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawSendTask(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // manual task
        activityDrawInstructions.put(ManualTask.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawManualTask(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // businessRuleTask task
        activityDrawInstructions.put(BusinessRuleTask.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawBusinessRuleTask(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // exclusive gateway
        activityDrawInstructions.put(ExclusiveGateway.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawExclusiveGateway(flowNode.getId(),
                                graphicInfo);
                    }
                });

        // inclusive gateway
        activityDrawInstructions.put(InclusiveGateway.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawInclusiveGateway(flowNode.getId(),
                                graphicInfo);
                    }
                });

        // parallel gateway
        activityDrawInstructions.put(ParallelGateway.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawParallelGateway(flowNode.getId(),
                                graphicInfo);
                    }
                });

        // event based gateway
        activityDrawInstructions.put(EventGateway.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawEventBasedGateway(flowNode.getId(),
                                graphicInfo);
                    }
                });

        // Boundary timer
        activityDrawInstructions.put(BoundaryEvent.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        BoundaryEvent boundaryEvent = (BoundaryEvent) flowNode;
                        if (boundaryEvent.getEventDefinitions() != null && !boundaryEvent.getEventDefinitions().isEmpty()) {
                            if (boundaryEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) {

                                processDiagramCanvas.drawCatchingTimerEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo,
                                        boundaryEvent.isCancelActivity());
                            } else if (boundaryEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) {

                                processDiagramCanvas.drawCatchingErrorEvent(flowNode.getId(),
                                        graphicInfo,
                                        boundaryEvent.isCancelActivity());
                            } else if (boundaryEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
                                processDiagramCanvas.drawCatchingSignalEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo,
                                        boundaryEvent.isCancelActivity());
                            } else if (boundaryEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) {
                                processDiagramCanvas.drawCatchingMessageEvent(flowNode.getId(),
                                        flowNode.getName(),
                                        graphicInfo,
                                        boundaryEvent.isCancelActivity());
                            } else if (boundaryEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) {
                                processDiagramCanvas.drawCatchingCompensateEvent(flowNode.getId(),
                                        graphicInfo,
                                        boundaryEvent.isCancelActivity());
                            }
                        }
                    }
                });

        // subprocess
        activityDrawInstructions.put(SubProcess.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                            processDiagramCanvas.drawCollapsedSubProcess(flowNode.getId(),
                                    flowNode.getName(),
                                    graphicInfo,
                                    false);
                        } else {
                            processDiagramCanvas.drawExpandedSubProcess(flowNode.getId(),
                                    flowNode.getName(),
                                    graphicInfo,
                                    SubProcess.class);
                        }
                    }
                });
        // transaction
        activityDrawInstructions.put(Transaction.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                            processDiagramCanvas.drawCollapsedSubProcess(flowNode.getId(),
                                    flowNode.getName(),
                                    graphicInfo,
                                    false);
                        } else {
                            processDiagramCanvas.drawExpandedSubProcess(flowNode.getId(),
                                    flowNode.getName(),
                                    graphicInfo,
                                    Transaction.class);
                        }
                    }
                });

        // Event subprocess
        activityDrawInstructions.put(EventSubProcess.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) {
                            processDiagramCanvas.drawCollapsedSubProcess(flowNode.getId(),
                                    flowNode.getName(),
                                    graphicInfo,
                                    true);
                        } else {
                            processDiagramCanvas.drawExpandedSubProcess(flowNode.getId(),
                                    flowNode.getName(),
                                    graphicInfo,
                                    EventSubProcess.class);
                        }
                    }
                });

        // call activity
        activityDrawInstructions.put(CallActivity.class,
                new ActivityDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     FlowNode flowNode) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
                        processDiagramCanvas.drawCollapsedCallActivity(flowNode.getId(),
                                flowNode.getName(),
                                graphicInfo);
                    }
                });

        // text annotation
        artifactDrawInstructions.put(TextAnnotation.class,
                new ArtifactDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     Artifact artifact) {
                        GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
                        TextAnnotation textAnnotation = (TextAnnotation) artifact;
                        processDiagramCanvas.drawTextAnnotation(textAnnotation.getId(),
                                textAnnotation.getText(),
                                graphicInfo);
                    }
                });

        // association
        artifactDrawInstructions.put(Association.class,
                new ArtifactDrawInstruction() {

                    @Override
                    public void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                                     BpmnModel bpmnModel,
                                     Artifact artifact) {
                        Association association = (Association) artifact;
                        String sourceRef = association.getSourceRef();
                        String targetRef = association.getTargetRef();

                        // source and target can be instance of FlowElement or Artifact
                        BaseElement sourceElement = bpmnModel.getFlowElement(sourceRef);
                        BaseElement targetElement = bpmnModel.getFlowElement(targetRef);
                        if (sourceElement == null) {
                            sourceElement = bpmnModel.getArtifact(sourceRef);
                        }
                        if (targetElement == null) {
                            targetElement = bpmnModel.getArtifact(targetRef);
                        }
                        List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
                        graphicInfoList = connectionPerfectionizer(processDiagramCanvas,
                                bpmnModel,
                                sourceElement,
                                targetElement,
                                graphicInfoList);
                        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();
                                yPoints[0] = (int) previousGraphicInfo.getY();
                            }
                            xPoints[i] = (int) graphicInfo.getX();
                            yPoints[i] = (int) graphicInfo.getY();
                        }

                        AssociationDirection associationDirection = association.getAssociationDirection();
                        processDiagramCanvas.drawAssociation(xPoints,
                                yPoints,
                                associationDirection,
                                false);
                    }
                });
    }

    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel,
                                       List<String> highLightedActivities,
                                       List<String> highLightedFlows,
                                       String activityFontName,
                                       String labelFontName,
                                       String annotationFontName) {
        return generateDiagram(bpmnModel,
                highLightedActivities,
                highLightedFlows,
                activityFontName,
                labelFontName,
                annotationFontName,
                false,
                null);
    }

    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel,
                                       List<String> highLightedActivities,
                                       List<String> highLightedFlows,
                                       String activityFontName,
                                       String labelFontName,
                                       String annotationFontName,
                                       boolean generateDefaultDiagram) {
        return generateDiagram(bpmnModel,
                highLightedActivities,
                highLightedFlows,
                activityFontName,
                labelFontName,
                annotationFontName,
                generateDefaultDiagram,
                null);
    }

    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel,
                                       List<String> highLightedActivities,
                                       List<String> highLightedFlows,
                                       String activityFontName,
                                       String labelFontName,
                                       String annotationFontName,
                                       boolean generateDefaultDiagram,
                                       String defaultDiagramImageFileName) {

        if (!bpmnModel.hasDiagramInterchangeInfo()) {
            if (!generateDefaultDiagram) {
                throw new ActivitiInterchangeInfoNotFoundException("No interchange information found.");
            }

            return getDefaultDiagram(defaultDiagramImageFileName);
        }

        return generateProcessDiagram(bpmnModel,
                highLightedActivities,
                highLightedFlows,
                activityFontName,
                labelFontName,
                annotationFontName).generateImage();
    }

    /**
     * Get default diagram image as bytes array
     * @return the default diagram image
     */
    protected InputStream getDefaultDiagram(String diagramImageFileName) {
        String imageFileName = diagramImageFileName != null ?
                diagramImageFileName :
                getDefaultDiagramImageFileName();
        InputStream imageStream = getClass().getResourceAsStream(imageFileName);
        if (imageStream == null) {
            throw new ActivitiImageException("Error occurred while getting default diagram image from file: " + imageFileName);
        }
        return imageStream;
    }

    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel,
                                       List<String> highLightedActivities,
                                       List<String> highLightedFlows) {
        return generateDiagram(bpmnModel,
                highLightedActivities,
                highLightedFlows,
                null,
                null,
                null,
                false,
                null);
    }

    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel,
                                       List<String> highLightedActivities) {
        return generateDiagram(bpmnModel,
                highLightedActivities,
                Collections.<String>emptyList());
    }

    @Override
    public InputStream generateDiagram(BpmnModel bpmnModel,
                                       String activityFontName,
                                       String labelFontName,
                                       String annotationFontName) {

        return generateDiagram(bpmnModel,
                Collections.<String>emptyList(),
                Collections.<String>emptyList(),
                activityFontName,
                labelFontName,
                annotationFontName);
    }

    protected DefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel,
                                                                                         List<String> highLightedActivities,
                                                                                         List<String> highLightedFlows,
                                                                                         String activityFontName,
                                                                                         String labelFontName,
                                                                                         String annotationFontName) {

        prepareBpmnModel(bpmnModel);

        DefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel,
                activityFontName,
                labelFontName,
                annotationFontName);

        // Draw pool shape, if process is participant in collaboration
        for (Pool pool : bpmnModel.getPools()) {
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
            processDiagramCanvas.drawPoolOrLane(pool.getId(),
                    pool.getName(),
                    graphicInfo);
        }

        // Draw lanes
        for (Process process : bpmnModel.getProcesses()) {
            for (Lane lane : process.getLanes()) {
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId());
                processDiagramCanvas.drawPoolOrLane(lane.getId(),
                        lane.getName(),
                        graphicInfo);
            }
        }

        // Draw activities and their sequence-flows
        for (Process process : bpmnModel.getProcesses()) {
            for (FlowNode flowNode : process.findFlowElementsOfType(FlowNode.class)) {
                drawActivity(processDiagramCanvas,
                        bpmnModel,
                        flowNode,
                        highLightedActivities,
                        highLightedFlows);
            }
        }

        // Draw artifacts
        for (Process process : bpmnModel.getProcesses()) {

            for (Artifact artifact : process.getArtifacts()) {
                drawArtifact(processDiagramCanvas,
                        bpmnModel,
                        artifact);
            }

            List<SubProcess> subProcesses = process.findFlowElementsOfType(SubProcess.class,
                    true);
            if (subProcesses != null) {
                for (SubProcess subProcess : subProcesses) {
                    for (Artifact subProcessArtifact : subProcess.getArtifacts()) {
                        drawArtifact(processDiagramCanvas,
                                bpmnModel,
                                subProcessArtifact);
                    }
                }
            }
        }

        return processDiagramCanvas;
    }

    protected void prepareBpmnModel(BpmnModel bpmnModel) {

        // Need to make sure all elements have positive x and y.
        // Check all graphicInfo and update the elements accordingly

        List<GraphicInfo> allGraphicInfos = new ArrayList<GraphicInfo>();
        if (bpmnModel.getLocationMap() != null) {
            allGraphicInfos.addAll(bpmnModel.getLocationMap().values());
        }
        if (bpmnModel.getLabelLocationMap() != null) {
            allGraphicInfos.addAll(bpmnModel.getLabelLocationMap().values());
        }
        if (bpmnModel.getFlowLocationMap() != null) {
            for (List<GraphicInfo> flowGraphicInfos : bpmnModel.getFlowLocationMap().values()) {
                allGraphicInfos.addAll(flowGraphicInfos);
            }
        }

        if (allGraphicInfos.size() > 0) {

            boolean needsTranslationX = false;
            boolean needsTranslationY = false;

            double lowestX = 0.0;
            double lowestY = 0.0;

            // Collect lowest x and y
            for (GraphicInfo graphicInfo : allGraphicInfos) {

                double x = graphicInfo.getX();
                double y = graphicInfo.getY();

                if (x < lowestX) {
                    needsTranslationX = true;
                    lowestX = x;
                }
                if (y < lowestY) {
                    needsTranslationY = true;
                    lowestY = y;
                }
            }

            // Update all graphicInfo objects
            if (needsTranslationX || needsTranslationY) {

                double translationX = Math.abs(lowestX);
                double translationY = Math.abs(lowestY);

                for (GraphicInfo graphicInfo : allGraphicInfos) {
                    if (needsTranslationX) {
                        graphicInfo.setX(graphicInfo.getX() + translationX);
                    }
                    if (needsTranslationY) {
                        graphicInfo.setY(graphicInfo.getY() + translationY);
                    }
                }
            }
        }
    }

    protected void drawActivity(DefaultProcessDiagramCanvas processDiagramCanvas,
                                BpmnModel bpmnModel,
                                FlowNode flowNode,
                                List<String> highLightedActivities,
                                List<String> highLightedFlows) {

        ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass());
        if (drawInstruction != null) {

            drawInstruction.draw(processDiagramCanvas,
                    bpmnModel,
                    flowNode);

            // Gather info on the multi instance marker
            boolean multiInstanceSequential = false;
            boolean multiInstanceParallel = false;
            boolean collapsed = false;
            if (flowNode instanceof Activity) {
                Activity activity = (Activity) flowNode;
                MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics();
                if (multiInstanceLoopCharacteristics != null) {
                    multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential();
                    multiInstanceParallel = !multiInstanceSequential;
                }
            }

            // Gather info on the collapsed marker
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            if (flowNode instanceof SubProcess) {
                collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded();
            } else if (flowNode instanceof CallActivity) {
                collapsed = true;
            }

            // Actually draw the markers
            processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(),
                    (int) graphicInfo.getY(),
                    (int) graphicInfo.getWidth(),
                    (int) graphicInfo.getHeight(),
                    multiInstanceSequential,
                    multiInstanceParallel,
                    collapsed);

            // Draw highlighted activities
            if (highLightedActivities.contains(flowNode.getId())) {
                drawHighLight(processDiagramCanvas,
                        bpmnModel.getGraphicInfo(flowNode.getId()));
            }
        }

        // Outgoing transitions of activity
        for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
            boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId()));
            String defaultFlow = null;
            if (flowNode instanceof Activity) {
                defaultFlow = ((Activity) flowNode).getDefaultFlow();
            } else if (flowNode instanceof Gateway) {
                defaultFlow = ((Gateway) flowNode).getDefaultFlow();
            }

            boolean isDefault = false;
            if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) {
                isDefault = true;
            }
            boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && !(flowNode instanceof Gateway);

            String sourceRef = sequenceFlow.getSourceRef();
            String targetRef = sequenceFlow.getTargetRef();
            FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef);
            FlowElement targetElement = bpmnModel.getFlowElement(targetRef);
            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
            if (graphicInfoList != null && graphicInfoList.size() > 0) {
                graphicInfoList = connectionPerfectionizer(processDiagramCanvas,
                        bpmnModel,
                        sourceElement,
                        targetElement,
                        graphicInfoList);
                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();
                        yPoints[0] = (int) previousGraphicInfo.getY();
                    }
                    xPoints[i] = (int) graphicInfo.getX();
                    yPoints[i] = (int) graphicInfo.getY();
                }

                processDiagramCanvas.drawSequenceflow(xPoints,
                        yPoints,
                        drawConditionalIndicator,
                        isDefault,
                        highLighted);

                // Draw sequenceflow label
                GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId());
                if (labelGraphicInfo != null) {
                    processDiagramCanvas.drawLabel(sequenceFlow.getName(),
                            labelGraphicInfo,
                            false);
                }
            }
        }

        // Nested elements
        if (flowNode instanceof FlowElementsContainer) {
            for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) {
                if (nestedFlowElement instanceof FlowNode) {
                    drawActivity(processDiagramCanvas,
                            bpmnModel,
                            (FlowNode) nestedFlowElement,
                            highLightedActivities,
                            highLightedFlows);
                }
            }
        }
    }

    /**
     * This method makes coordinates of connection flow better.
     * @param processDiagramCanvas
     * @param bpmnModel
     * @param sourceElement
     * @param targetElement
     * @param graphicInfoList
     * @return
     */
    protected static List<GraphicInfo> connectionPerfectionizer(DefaultProcessDiagramCanvas processDiagramCanvas,
                                                                BpmnModel bpmnModel,
                                                                BaseElement sourceElement,
                                                                BaseElement targetElement,
                                                                List<GraphicInfo> graphicInfoList) {
        GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId());
        GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement.getId());

        DefaultProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement);
        DefaultProcessDiagramCanvas.SHAPE_TYPE targetShapeType = getShapeType(targetElement);

        return processDiagramCanvas.connectionPerfectionizer(sourceShapeType,
                targetShapeType,
                sourceGraphicInfo,
                targetGraphicInfo,
                graphicInfoList);
    }

    /**
     * This method returns shape type of base element.<br>
     * Each element can be presented as rectangle, rhombus, or ellipse.
     * @param baseElement
     * @return DefaultProcessDiagramCanvas.SHAPE_TYPE
     */
    protected static DefaultProcessDiagramCanvas.SHAPE_TYPE getShapeType(BaseElement baseElement) {
        if (baseElement instanceof Task || baseElement instanceof Activity || baseElement instanceof TextAnnotation) {
            return DefaultProcessDiagramCanvas.SHAPE_TYPE.Rectangle;
        } else if (baseElement instanceof Gateway) {
            return DefaultProcessDiagramCanvas.SHAPE_TYPE.Rhombus;
        } else if (baseElement instanceof Event) {
            return DefaultProcessDiagramCanvas.SHAPE_TYPE.Ellipse;
        }
        // unknown source element, just do not correct coordinates
        return null;
    }

    protected static GraphicInfo getLineCenter(List<GraphicInfo> graphicInfoList) {
        GraphicInfo gi = new GraphicInfo();

        int xPoints[] = new int[graphicInfoList.size()];
        int yPoints[] = new int[graphicInfoList.size()];

        double length = 0;
        double[] lengths = new double[graphicInfoList.size()];
        lengths[0] = 0;
        double m;
        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();
                yPoints[0] = (int) previousGraphicInfo.getY();
            }
            xPoints[i] = (int) graphicInfo.getX();
            yPoints[i] = (int) graphicInfo.getY();

            length += Math.sqrt(
                    Math.pow((int) graphicInfo.getX() - (int) previousGraphicInfo.getX(),
                            2) +
                            Math.pow((int) graphicInfo.getY() - (int) previousGraphicInfo.getY(),
                                    2)
            );
            lengths[i] = length;
        }
        m = length / 2;
        int p1 = 0;
        int p2 = 1;
        for (int i = 1; i < lengths.length; i++) {
            double len = lengths[i];
            p1 = i - 1;
            p2 = i;
            if (len > m) {
                break;
            }
        }

        GraphicInfo graphicInfo1 = graphicInfoList.get(p1);
        GraphicInfo graphicInfo2 = graphicInfoList.get(p2);

        double AB = (int) graphicInfo2.getX() - (int) graphicInfo1.getX();
        double OA = (int) graphicInfo2.getY() - (int) graphicInfo1.getY();
        double OB = lengths[p2] - lengths[p1];
        double ob = m - lengths[p1];
        double ab = AB * ob / OB;
        double oa = OA * ob / OB;

        double mx = graphicInfo1.getX() + ab;
        double my = graphicInfo1.getY() + oa;

        gi.setX(mx);
        gi.setY(my);
        return gi;
    }

    protected void drawArtifact(DefaultProcessDiagramCanvas processDiagramCanvas,
                                BpmnModel bpmnModel,
                                Artifact artifact) {

        ArtifactDrawInstruction drawInstruction = artifactDrawInstructions.get(artifact.getClass());
        if (drawInstruction != null) {
            drawInstruction.draw(processDiagramCanvas,
                    bpmnModel,
                    artifact);
        }
    }

    private static void drawHighLight(DefaultProcessDiagramCanvas processDiagramCanvas,
                                      GraphicInfo graphicInfo) {
        processDiagramCanvas.drawHighLight((int) graphicInfo.getX(),
                (int) graphicInfo.getY(),
                (int) graphicInfo.getWidth(),
                (int) graphicInfo.getHeight());
    }

    private static void drawHighLight(DefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo, Color color, FlowNode flowNode) {
        if(flowNode instanceof Event){
            processDiagramCanvas.drawHighLightEvent((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight(),color);
        }else if(flowNode instanceof Gateway){
            processDiagramCanvas.drawHighLightGateway((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight(),color);
        }else if(flowNode instanceof Task){
            processDiagramCanvas.drawHighLightTask((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight(),color);
        }else if(flowNode instanceof SubProcess){
            processDiagramCanvas.drawHighLightSubProcess((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight(),color);
        }else{
            processDiagramCanvas.drawHighLight((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight(),color);
        }
    }

    protected static DefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel,
                                                                                                  String activityFontName,
                                                                                                  String labelFontName,
                                                                                                  String annotationFontName) {

        // We need to calculate maximum values to know how big the image will be in its entirety
        double minX = Double.MAX_VALUE;
        double maxX = 0;
        double minY = Double.MAX_VALUE;
        double maxY = 0;

        for (Pool pool : bpmnModel.getPools()) {
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
            minX = graphicInfo.getX();
            maxX = graphicInfo.getX() + graphicInfo.getWidth();
            minY = graphicInfo.getY();
            maxY = graphicInfo.getY() + graphicInfo.getHeight();
        }

        List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
        for (FlowNode flowNode : flowNodes) {

            GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());

            if (flowNodeGraphicInfo == null) {
                continue;
            }

            // width
            if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
                maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
            }
            if (flowNodeGraphicInfo.getX() < minX) {
                minX = flowNodeGraphicInfo.getX();
            }
            // height
            if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
                maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
            }
            if (flowNodeGraphicInfo.getY() < minY) {
                minY = flowNodeGraphicInfo.getY();
            }

            for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
                List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
                if (graphicInfoList != null) {
                    for (GraphicInfo graphicInfo : graphicInfoList) {
                        // width
                        if (graphicInfo.getX() > maxX) {
                            maxX = graphicInfo.getX();
                        }
                        if (graphicInfo.getX() < minX) {
                            minX = graphicInfo.getX();
                        }
                        // height
                        if (graphicInfo.getY() > maxY) {
                            maxY = graphicInfo.getY();
                        }
                        if (graphicInfo.getY() < minY) {
                            minY = 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() > maxX) {
                    maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth();
                }
                if (artifactGraphicInfo.getX() < minX) {
                    minX = artifactGraphicInfo.getX();
                }
                // height
                if (artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) {
                    maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight();
                }
                if (artifactGraphicInfo.getY() < minY) {
                    minY = artifactGraphicInfo.getY();
                }
            }

            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
            if (graphicInfoList != null) {
                for (GraphicInfo graphicInfo : graphicInfoList) {
                    // width
                    if (graphicInfo.getX() > maxX) {
                        maxX = graphicInfo.getX();
                    }
                    if (graphicInfo.getX() < minX) {
                        minX = graphicInfo.getX();
                    }
                    // height
                    if (graphicInfo.getY() > maxY) {
                        maxY = graphicInfo.getY();
                    }
                    if (graphicInfo.getY() < minY) {
                        minY = graphicInfo.getY();
                    }
                }
            }
        }

        int nrOfLanes = 0;
        for (Process process : bpmnModel.getProcesses()) {
            for (Lane l : process.getLanes()) {

                nrOfLanes++;

                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
                if (graphicInfo != null) {
                    // width
                    if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
                        maxX = graphicInfo.getX() + graphicInfo.getWidth();
                    }
                    if (graphicInfo.getX() < minX) {
                        minX = graphicInfo.getX();
                    }
                    // height
                    if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
                        maxY = graphicInfo.getY() + graphicInfo.getHeight();
                    }
                    if (graphicInfo.getY() < minY) {
                        minY = graphicInfo.getY();
                    }
                }
            }
        }

        // Special case, see https://activiti.atlassian.net/browse/ACT-1431
        if (flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) {
            // Nothing to show
            minX = 0;
            minY = 0;
        }

        return new DefaultProcessDiagramCanvas((int) maxX + 10,
                (int) maxY + 10,
                (int) minX,
                (int) minY,
                activityFontName,
                labelFontName,
                annotationFontName);
    }

    protected static List<Artifact> gatherAllArtifacts(BpmnModel bpmnModel) {
        List<Artifact> artifacts = new ArrayList<Artifact>();
        for (Process process : bpmnModel.getProcesses()) {
            artifacts.addAll(process.getArtifacts());
        }
        return artifacts;
    }

    protected static List<FlowNode> gatherAllFlowNodes(BpmnModel bpmnModel) {
        List<FlowNode> flowNodes = new ArrayList<FlowNode>();
        for (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 Map<Class<? extends BaseElement>, ActivityDrawInstruction> getActivityDrawInstructions() {
        return activityDrawInstructions;
    }

    public void setActivityDrawInstructions(
            Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions) {
        this.activityDrawInstructions = activityDrawInstructions;
    }

    public Map<Class<? extends BaseElement>, ArtifactDrawInstruction> getArtifactDrawInstructions() {
        return artifactDrawInstructions;
    }

    public void setArtifactDrawInstructions(
            Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions) {
        this.artifactDrawInstructions = artifactDrawInstructions;
    }

    protected interface ActivityDrawInstruction {

        void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                  BpmnModel bpmnModel,
                  FlowNode flowNode);
    }

    protected interface ArtifactDrawInstruction {

        void draw(DefaultProcessDiagramCanvas processDiagramCanvas,
                  BpmnModel bpmnModel,
                  Artifact artifact);
    }

    public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,List<String> highLightedFinishes, List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, List<String> runningActivityIdList) {
        return this.generateProcessDiagram(bpmnModel, imageType,highLightedFinishes, highLightedActivities, highLightedFlows, activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor, runningActivityIdList).generateImage();
    }

    protected DefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType,List<String> highLightedFinishes, List<String> highLightedActivities, List<String> highLightedFlows, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, List<String> runningActivityIdList) {
        this.prepareBpmnModel(bpmnModel);
        DefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
        Iterator var12 = bpmnModel.getPools().iterator();

        while(var12.hasNext()) {
            Pool pool = (Pool)var12.next();
            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
            processDiagramCanvas.drawPoolOrLane(pool.getId(), pool.getName(), graphicInfo);
        }

        var12 = bpmnModel.getProcesses().iterator();

        Process process;
        Iterator var20;
        while(var12.hasNext()) {
            process = (Process)var12.next();
            var20 = process.getLanes().iterator();

            while(var20.hasNext()) {
                Lane lane = (Lane)var20.next();
                GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId());
                processDiagramCanvas.drawPoolOrLane(lane.getId(), lane.getName(), graphicInfo);
            }
        }

        var12 = bpmnModel.getProcesses().iterator();
        while(var12.hasNext()) {
            process = (Process)var12.next();
            var20 = process.findFlowElementsOfType(FlowNode.class).iterator();

            while(var20.hasNext()) {
                FlowNode flowNode = (FlowNode)var20.next();
                this.drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedFinishes,highLightedActivities, highLightedFlows, scaleFactor, runningActivityIdList);
            }
        }

        var12 = bpmnModel.getProcesses().iterator();

        while(true) {
            List subProcesses;
            do {
                if(!var12.hasNext()) {
                    return processDiagramCanvas;
                }

                process = (Process)var12.next();
                var20 = process.getArtifacts().iterator();

                while(var20.hasNext()) {
                    Artifact artifact = (Artifact)var20.next();
                    this.drawArtifact(processDiagramCanvas, bpmnModel, artifact);
                }

                subProcesses = process.findFlowElementsOfType(SubProcess.class, true);
            } while(subProcesses == null);

            Iterator var24 = subProcesses.iterator();

            while(var24.hasNext()) {
                SubProcess subProcess = (SubProcess)var24.next();
                Iterator var17 = subProcess.getArtifacts().iterator();

                while(var17.hasNext()) {
                    Artifact subProcessArtifact = (Artifact)var17.next();
                    this.drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact);
                }
            }
        }
    }

    protected static DefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        //double minX = 1.7976931348623157E308D;
        //double maxX = 0.0D;
        //double minY = 1.7976931348623157E308D;
        //double maxY = 0.0D;

        double minX = 10;
        double maxX = 2;
        double minY = 6;
        double maxY = 0.0D;

        GraphicInfo graphicInfo;
        for(Iterator var14 = bpmnModel.getPools().iterator(); var14.hasNext(); maxY = graphicInfo.getY() + graphicInfo.getHeight()) {
            Pool pool = (Pool)var14.next();
            graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
            minX = graphicInfo.getX();
            maxX = graphicInfo.getX() + graphicInfo.getWidth();
            minY = graphicInfo.getY();
        }

        List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
        Iterator var24 = flowNodes.iterator();

        label155:
        while(var24.hasNext()) {
            FlowNode flowNode = (FlowNode)var24.next();
            GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            if(flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
                maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth();
            }

            if(flowNodeGraphicInfo.getX() < minX) {
                minX = flowNodeGraphicInfo.getX();
            }

            if(flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
                maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight();
            }

            if(flowNodeGraphicInfo.getY() < minY) {
                minY = flowNodeGraphicInfo.getY();
            }

            Iterator var18 = flowNode.getOutgoingFlows().iterator();

            while(true) {
                List graphicInfoList;
                do {
                    if(!var18.hasNext()) {
                        continue label155;
                    }

                    SequenceFlow sequenceFlow = (SequenceFlow)var18.next();
                    graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
                } while(graphicInfoList == null);

                Iterator var21 = graphicInfoList.iterator();

                while(var21.hasNext()) {
                    graphicInfo = (GraphicInfo)var21.next();
                    if(graphicInfo.getX() > maxX) {
                        maxX = graphicInfo.getX();
                    }

                    if(graphicInfo.getX() < minX) {
                        minX = graphicInfo.getX();
                    }

                    if(graphicInfo.getY() > maxY) {
                        maxY = graphicInfo.getY();
                    }

                    if(graphicInfo.getY() < minY) {
                        minY = graphicInfo.getY();
                    }
                }
            }
        }

        List<Artifact> artifacts = gatherAllArtifacts(bpmnModel);
        Iterator var27 = artifacts.iterator();

        while(var27.hasNext()) {
            Artifact artifact = (Artifact)var27.next();
            GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact.getId());
            if(artifactGraphicInfo != null) {
                if(artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) {
                    maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth();
                }

                if(artifactGraphicInfo.getX() < minX) {
                    minX = artifactGraphicInfo.getX();
                }

                if(artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) {
                    maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight();
                }

                if(artifactGraphicInfo.getY() < minY) {
                    minY = artifactGraphicInfo.getY();
                }
            }

            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId());
            if(graphicInfoList != null) {
                Iterator var35 = graphicInfoList.iterator();

                while(var35.hasNext()) {
                    graphicInfo = (GraphicInfo)var35.next();
                    if(graphicInfo.getX() > maxX) {
                        maxX = graphicInfo.getX();
                    }

                    if(graphicInfo.getX() < minX) {
                        minX = graphicInfo.getX();
                    }

                    if(graphicInfo.getY() > maxY) {
                        maxY = graphicInfo.getY();
                    }

                    if(graphicInfo.getY() < minY) {
                        minY = graphicInfo.getY();
                    }
                }
            }
        }

        int nrOfLanes = 0;
        Iterator var30 = bpmnModel.getProcesses().iterator();

        while(var30.hasNext()) {
            Process process = (Process)var30.next();
            Iterator var34 = process.getLanes().iterator();

            while(var34.hasNext()) {
                Lane l = (Lane)var34.next();
                ++nrOfLanes;
                graphicInfo = bpmnModel.getGraphicInfo(l.getId());
                if(graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
                    maxX = graphicInfo.getX() + graphicInfo.getWidth();
                }

                if(graphicInfo.getX() < minX) {
                    minX = graphicInfo.getX();
                }

                if(graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
                    maxY = graphicInfo.getY() + graphicInfo.getHeight();
                }

                if(graphicInfo.getY() < minY) {
                    minY = graphicInfo.getY();
                }
            }
        }

        if(flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) {
            minX = 0.0D;
            minY = 0.0D;
        }

        return new DefaultProcessDiagramCanvas((int)maxX + 10, (int)maxY + 10, (int)minX, (int)minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
    }

    protected void drawActivity(DefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode,List<String> highLightedFinishes, List<String> highLightedActivities, List<String> highLightedFlows, double scaleFactor, List<String> runningActivityIdList) {
        ActivityDrawInstruction drawInstruction = this.activityDrawInstructions.get(flowNode.getClass());
        boolean highLighted;
        if(drawInstruction != null) {
            drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode);
            boolean multiInstanceSequential = false;
            boolean multiInstanceParallel = false;
            highLighted = false;
            if(flowNode instanceof Activity) {
                Activity activity = (Activity)flowNode;
                MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics();
                if(multiInstanceLoopCharacteristics != null) {
                    multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential();
                    multiInstanceParallel = !multiInstanceSequential;
                }
            }

            GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId());
            if(!(flowNode instanceof SubProcess)) {
                if(flowNode instanceof CallActivity) {
                    highLighted = true;
                }
            } else {
                highLighted = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded().booleanValue();
            }

            if(scaleFactor == 1.0D) {
                processDiagramCanvas.drawActivityMarkers((int)graphicInfo.getX(), (int)graphicInfo.getY(), (int)graphicInfo.getWidth(), (int)graphicInfo.getHeight(), multiInstanceSequential, multiInstanceParallel, highLighted);
            }
            // 历史节点高亮
            if(highLightedActivities.contains(flowNode.getId())) {
                drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()),DefaultProcessDiagramCanvas.HIGHLIGHT_COLOR,flowNode);
            }

           // if(highLightedFinishes.contains(flowNode.getId()) && !highLightedActivities.contains(flowNode.getId())) {
            // 正在执行的节点高亮
            if(runningActivityIdList.contains(flowNode.getId())) {

                drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId()),DefaultProcessDiagramCanvas.FINISHHIGHLIGHT_COLOR,flowNode);
            }
        }

        Iterator var25 = flowNode.getOutgoingFlows().iterator();

        while(var25.hasNext()) {
            SequenceFlow sequenceFlow = (SequenceFlow)var25.next();
            highLighted = highLightedFlows.contains(sequenceFlow.getId());
            String defaultFlow = null;
            if(flowNode instanceof Activity) {
                defaultFlow = ((Activity)flowNode).getDefaultFlow();
            } else if(flowNode instanceof Gateway) {
                defaultFlow = ((Gateway)flowNode).getDefaultFlow();
            }

            boolean isDefault = false;
            if(defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) {
                isDefault = true;
            }

            boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && !(flowNode instanceof Gateway);
            String sourceRef = sequenceFlow.getSourceRef();
            String targetRef = sequenceFlow.getTargetRef();
            FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef);
            FlowElement targetElement = bpmnModel.getFlowElement(targetRef);
            List<GraphicInfo> graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
            if(graphicInfoList != null && graphicInfoList.size() > 0) {
                graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList);
                int[] xPoints = new int[graphicInfoList.size()];
                int[] yPoints = new int[graphicInfoList.size()];

                for(int i = 1; i < graphicInfoList.size(); ++i) {
                    GraphicInfo graphicInfo = (GraphicInfo)graphicInfoList.get(i);
                    GraphicInfo previousGraphicInfo = (GraphicInfo)graphicInfoList.get(i - 1);
                    if(i == 1) {
                        xPoints[0] = (int)previousGraphicInfo.getX();
                        yPoints[0] = (int)previousGraphicInfo.getY();
                    }

                    xPoints[i] = (int)graphicInfo.getX();
                    yPoints[i] = (int)graphicInfo.getY();
                }

                processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, highLighted, scaleFactor);
                GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId());
                if(labelGraphicInfo != null) {
                    processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false);
                }
            }
        }

        if(flowNode instanceof FlowElementsContainer) {
            var25 = ((FlowElementsContainer)flowNode).getFlowElements().iterator();

            while(var25.hasNext()) {
                FlowElement nestedFlowElement = (FlowElement)var25.next();
                if(nestedFlowElement instanceof FlowNode) {
                    this.drawActivity(processDiagramCanvas, bpmnModel, (FlowNode)nestedFlowElement, highLightedFinishes,highLightedActivities, highLightedFlows, scaleFactor, runningActivityIdList);
                }
            }
        }

    }
}

  1. 重写DefaultProcessDiagramCanvas类
package com.gxkj.common.modular.activiti.config;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;

import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.EventSubProcess;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.bpmn.model.Transaction;
import org.activiti.image.exception.ActivitiImageException;
import org.activiti.image.impl.ProcessDiagramSVGGraphics2D;
import org.activiti.image.impl.icon.BusinessRuleTaskIconType;
import org.activiti.image.impl.icon.CompensateIconType;
import org.activiti.image.impl.icon.CompensateThrowIconType;
import org.activiti.image.impl.icon.ErrorIconType;
import org.activiti.image.impl.icon.ErrorThrowIconType;
import org.activiti.image.impl.icon.IconType;
import org.activiti.image.impl.icon.ManualTaskIconType;
import org.activiti.image.impl.icon.MessageIconType;
import org.activiti.image.impl.icon.ReceiveTaskIconType;
import org.activiti.image.impl.icon.ScriptTaskIconType;
import org.activiti.image.impl.icon.SendTaskIconType;
import org.activiti.image.impl.icon.ServiceTaskIconType;
import org.activiti.image.impl.icon.SignalIconType;
import org.activiti.image.impl.icon.SignalThrowIconType;
import org.activiti.image.impl.icon.TaskIconType;
import org.activiti.image.impl.icon.TimerIconType;
import org.activiti.image.impl.icon.UserTaskIconType;
import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2DIOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

import javax.imageio.ImageIO;

/**
 1. Represents a canvas on which BPMN 2.0 constructs can be drawn.
 2. <p>
 3. @see org.activiti.image.impl.DefaultProcessDiagramGenerator
 */
public class DefaultProcessDiagramCanvas {

    protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultProcessDiagramCanvas.class);

    public enum SHAPE_TYPE {
        Rectangle,
        Rhombus,
        Ellipse
    }

    // Predefined sized
    protected static final int ARROW_WIDTH = 5;
    protected static final int CONDITIONAL_INDICATOR_WIDTH = 16;
    protected static final int DEFAULT_INDICATOR_WIDTH = 10;
    protected static final int MARKER_WIDTH = 12;
    protected static final int FONT_SIZE = 11;
    protected static final int FONT_SPACING = 2;
    protected static final int TEXT_PADDING = 3;
    protected static final int ANNOTATION_TEXT_PADDING = 7;
    //protected static final int ARROW_WIDTH = 5;
    //protected static final int CONDITIONAL_INDICATOR_WIDTH = 13;
    //protected static final int DEFAULT_INDICATOR_WIDTH = 8;
    //protected static final int MARKER_WIDTH = 6;
    //protected static final int FONT_SIZE = 8;
    //protected static final int FONT_SPACING = 1;
    //protected static final int TEXT_PADDING = 2;
    //protected static final int ANNOTATION_TEXT_PADDING = 4;
    protected static final int LINE_HEIGHT = FONT_SIZE + FONT_SPACING;

    // Colors
    protected static Color TASK_BOX_COLOR = new Color(249,
            249,
            249);
    protected static Color SUBPROCESS_BOX_COLOR = new Color(255,
            255,
            255);
    protected static Color EVENT_COLOR = new Color(255,
            255,
            255);
    protected static Color CONNECTION_COLOR = new Color(88,
            88,
            88);
    protected static Color CONDITIONAL_INDICATOR_COLOR = new Color(255,
            255,
            255);
    protected static Color HIGHLIGHT_COLOR = Color.GREEN; // 已流转高亮节点颜色
    protected static Color FINISHHIGHLIGHT_COLOR = Color.decode("#F68535") ; // 当前节点颜色
    protected static Color LABEL_COLOR = new Color(112,
            146,
            190);
    protected static Color TASK_BORDER_COLOR = new Color(187,
            187,
            187);
    protected static Color EVENT_BORDER_COLOR = new Color(88,
            88,
            88);
    protected static Color SUBPROCESS_BORDER_COLOR = new Color(0,
            0,
            0);

    // Fonts
    protected static Font LABEL_FONT = null;
    protected static Font ANNOTATION_FONT = null;

    // Strokes
    protected static Stroke THICK_TASK_BORDER_STROKE = new BasicStroke(3.0f); // 流程节点圆圈大小
    protected static Stroke GATEWAY_TYPE_STROKE = new BasicStroke(3.0f);
    protected static Stroke END_EVENT_STROKE = new BasicStroke(3.0f); // 结束节点圆圈大小
    protected static Stroke MULTI_INSTANCE_STROKE = new BasicStroke(1.3f);
    protected static Stroke EVENT_SUBPROCESS_STROKE = new BasicStroke(1.0f,
            BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_MITER,
            1.0f,
            new float[]{1.0f},
            0.0f);
    protected static Stroke NON_INTERRUPTING_EVENT_STROKE = new BasicStroke(1.0f,
            BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_MITER,
            1.0f,
            new float[]{4.0f, 3.0f},
            0.0f);
    protected static Stroke HIGHLIGHT_FLOW_STROKE = new BasicStroke(1.3f);
    protected static Stroke ANNOTATION_STROKE = new BasicStroke(2.0f);
    protected static Stroke ASSOCIATION_STROKE = new BasicStroke(2.0f,
            BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_MITER,
            1.0f,
            new float[]{2.0f, 2.0f},
            0.0f);

    // icons
    protected static int ICON_PADDING = 5;
    protected static TaskIconType USERTASK_IMAGE;
    protected static TaskIconType SCRIPTTASK_IMAGE;
    protected static TaskIconType SERVICETASK_IMAGE;
    protected static TaskIconType RECEIVETASK_IMAGE;
    protected static TaskIconType SENDTASK_IMAGE;
    protected static TaskIconType MANUALTASK_IMAGE;
    protected static TaskIconType BUSINESS_RULE_TASK_IMAGE;

    protected static IconType TIMER_IMAGE;
    protected static IconType COMPENSATE_THROW_IMAGE;
    protected static IconType COMPENSATE_CATCH_IMAGE;
    protected static IconType ERROR_THROW_IMAGE;
    protected static IconType ERROR_CATCH_IMAGE;
    protected static IconType MESSAGE_CATCH_IMAGE;
    protected static IconType SIGNAL_CATCH_IMAGE;
    protected static IconType SIGNAL_THROW_IMAGE;

    protected int canvasWidth = -1;
    protected int canvasHeight = -1;
    protected int minX = -1;
    protected int minY = -1;
    protected ProcessDiagramSVGGraphics2D g;
    protected FontMetrics fontMetrics;
    protected boolean closed;
    protected BufferedImage processDiagram;
    protected ClassLoader customClassLoader;
    protected String activityFontName = "宋体";
    protected String labelFontName = "宋体";
    protected String annotationFontName = "宋体";

    /**
     * Creates an empty canvas with given width and height.
     * <p>
     * Allows to specify minimal boundaries on the left and upper side of the
     * canvas. This is useful for diagrams that have white space there.
     * Everything beneath these minimum values will be cropped.
     * It's also possible to pass a specific font name and a class loader for the icon images.
     */
    public DefaultProcessDiagramCanvas(int width,
                                       int height,
                                       int minX,
                                       int minY,
                                       String activityFontName,
                                       String labelFontName,
                                       String annotationFontName) {

        this.canvasWidth = width;
        this.canvasHeight = height;
        this.minX = minX;
        this.minY = minY;
        if (activityFontName != null) {
            this.activityFontName = activityFontName;
        }
        if (labelFontName != null) {
            this.labelFontName = labelFontName;
        }
        if (annotationFontName != null) {
            this.annotationFontName = annotationFontName;
        }

        initialize();
    }

    public DefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
        this.canvasWidth = width;
        this.canvasHeight = height;
        this.minX = minX;
        this.minY = minY;
        if(activityFontName != null) {
            this.activityFontName = activityFontName;
        }

        if(labelFontName != null) {
            this.labelFontName = labelFontName;
        }

        if(annotationFontName != null) {
            this.annotationFontName = annotationFontName;
        }

        this.customClassLoader = customClassLoader;
        this.initialize(imageType);
    }

    /**
     * Creates an empty canvas with given width and height.
     * <p>
     * Allows to specify minimal boundaries on the left and upper side of the
     * canvas. This is useful for diagrams that have white space there (eg
     * Signavio). Everything beneath these minimum values will be cropped.
     * @param minX Hint that will be used when generating the image. Parts that fall
     * below minX on the horizontal scale will be cropped.
     * @param minY Hint that will be used when generating the image. Parts that fall
     * below minX on the horizontal scale will be cropped.
     */
    public DefaultProcessDiagramCanvas(int width,
                                       int height,
                                       int minX,
                                       int minY) {
        this.canvasWidth = width;
        this.canvasHeight = height;
        this.minX = minX;
        this.minY = minY;

        initialize();
    }

    public void initialize(String imageType) {
        if("png".equalsIgnoreCase(imageType)) {
            this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 2);
        } else {
            this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 1);
        }

        // Get a DOMImplementation.
        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();

        // Create an instance of org.w3c.dom.Document.
        String svgNS = "http://www.w3.org/2000/svg";
        Document document = domImpl.createDocument(svgNS,
                "svg",
                null);

        // Create an instance of the SVG Generator.
        this.g = new ProcessDiagramSVGGraphics2D(document);

        this.g.setSVGCanvasSize(new Dimension(this.canvasWidth, this.canvasHeight));

        this.g.setBackground(new Color(255,
                255,
                255,
                0));
        this.g.clearRect(0,
                0,
                canvasWidth,
                canvasHeight);

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g.setPaint(Color.black);

        Font font = new Font(activityFontName,
                Font.BOLD,
                FONT_SIZE);
        g.setFont(font);
        this.fontMetrics = g.getFontMetrics();

        LABEL_FONT = new Font(labelFontName,
                Font.ITALIC,
                10);
        ANNOTATION_FONT = new Font(annotationFontName,
                Font.PLAIN,
                FONT_SIZE);

        USERTASK_IMAGE = new UserTaskIconType();
        SCRIPTTASK_IMAGE = new ScriptTaskIconType();
        SERVICETASK_IMAGE = new ServiceTaskIconType();
        RECEIVETASK_IMAGE = new ReceiveTaskIconType();
        SENDTASK_IMAGE = new SendTaskIconType();
        MANUALTASK_IMAGE = new ManualTaskIconType();
        BUSINESS_RULE_TASK_IMAGE = new BusinessRuleTaskIconType();

        TIMER_IMAGE = new TimerIconType();
        COMPENSATE_THROW_IMAGE = new CompensateThrowIconType();
        COMPENSATE_CATCH_IMAGE = new CompensateIconType();
        ERROR_THROW_IMAGE = new ErrorThrowIconType();
        ERROR_CATCH_IMAGE = new ErrorIconType();
        MESSAGE_CATCH_IMAGE = new MessageIconType();
        SIGNAL_THROW_IMAGE = new SignalThrowIconType();
        SIGNAL_CATCH_IMAGE = new SignalIconType();
    }

    public void initialize() {
        // Get a DOMImplementation.
        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();

        // Create an instance of org.w3c.dom.Document.
        String svgNS = "http://www.w3.org/2000/svg";
        Document document = domImpl.createDocument(svgNS,
                "svg",
                null);

        // Create an instance of the SVG Generator.
        this.g = new ProcessDiagramSVGGraphics2D(document);

        this.g.setSVGCanvasSize(new Dimension(this.canvasWidth, this.canvasHeight));

        this.g.setBackground(new Color(255,
                255,
                255,
                0));
        this.g.clearRect(0,
                0,
                canvasWidth,
                canvasHeight);

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g.setPaint(Color.black);

        Font font = new Font(activityFontName,
                Font.BOLD,
                FONT_SIZE);
        g.setFont(font);
        this.fontMetrics = g.getFontMetrics();

        LABEL_FONT = new Font(labelFontName,
                Font.ITALIC,
                10);
        ANNOTATION_FONT = new Font(annotationFontName,
                Font.PLAIN,
                FONT_SIZE);

        USERTASK_IMAGE = new UserTaskIconType();
        SCRIPTTASK_IMAGE = new ScriptTaskIconType();
        SERVICETASK_IMAGE = new ServiceTaskIconType();
        RECEIVETASK_IMAGE = new ReceiveTaskIconType();
        SENDTASK_IMAGE = new SendTaskIconType();
        MANUALTASK_IMAGE = new ManualTaskIconType();
        BUSINESS_RULE_TASK_IMAGE = new BusinessRuleTaskIconType();

        TIMER_IMAGE = new TimerIconType();
        COMPENSATE_THROW_IMAGE = new CompensateThrowIconType();
        COMPENSATE_CATCH_IMAGE = new CompensateIconType();
        ERROR_THROW_IMAGE = new ErrorThrowIconType();
        ERROR_CATCH_IMAGE = new ErrorIconType();
        MESSAGE_CATCH_IMAGE = new MessageIconType();
        SIGNAL_THROW_IMAGE = new SignalThrowIconType();
        SIGNAL_CATCH_IMAGE = new SignalIconType();
    }

    /**
     * Generates an image of what currently is drawn on the canvas.
     * <p>
     * Throws an {@link ActivitiImageException} when {@link #close()} is already
     * called.
     */
    public InputStream generateImage() {
        if (closed) {
            throw new ActivitiImageException("ProcessDiagramGenerator already closed");
        }

        try {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            Writer out;
            out = new OutputStreamWriter(stream,
                    "UTF-8");
            g.stream(out,
                    true);
            return new ByteArrayInputStream(stream.toByteArray());
        } catch (UnsupportedEncodingException | SVGGraphics2DIOException e) {
            throw new ActivitiImageException("Error while generating process image",
                    e);
        }
    }

    /**
     * Closes the canvas which dissallows further drawing and releases graphical
     * resources.
     */
    public void close() {
        g.dispose();
        closed = true;
    }

    public void drawNoneStartEvent(String id,
                                   GraphicInfo graphicInfo) {
        drawStartEvent(id,
                graphicInfo,
                null);
    }

    public void drawTimerStartEvent(String id,
                                    GraphicInfo graphicInfo) {
        drawStartEvent(id,
                graphicInfo,
                TIMER_IMAGE);
    }

    public void drawSignalStartEvent(String id,
                                     GraphicInfo graphicInfo) {
        drawStartEvent(id,
                graphicInfo,
                SIGNAL_CATCH_IMAGE);
    }

    public void drawMessageStartEvent(String id,
                                      GraphicInfo graphicInfo) {
        drawStartEvent(id,
                graphicInfo,
                MESSAGE_CATCH_IMAGE);
    }

    public void drawStartEvent(String id,
                               GraphicInfo graphicInfo,
                               IconType icon) {
        Paint originalPaint = g.getPaint();
        g.setPaint(EVENT_COLOR);
        Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(),
                graphicInfo.getY(),
                graphicInfo.getWidth(),
                graphicInfo.getHeight());
        g.fill(circle);
        g.setPaint(EVENT_BORDER_COLOR);
        g.draw(circle);
        g.setPaint(originalPaint);

        // calculate coordinates to center image
        if (icon != null) {
            int imageX = (int) Math.round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (icon.getWidth() / 2));
            int imageY = (int) Math.round(graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (icon.getHeight() / 2));

            icon.drawIcon(imageX,
                    imageY,
                    ICON_PADDING,
                    g);
        }

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawNoneEndEvent(String id,
                                 String name,
                                 GraphicInfo graphicInfo) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(EVENT_COLOR);
        Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(),
                graphicInfo.getY(),
                graphicInfo.getWidth(),
                graphicInfo.getHeight());
        g.fill(circle);
        g.setPaint(EVENT_BORDER_COLOR);
        g.setStroke(END_EVENT_STROKE);
        g.draw(circle);
        g.setStroke(originalStroke);
        g.setPaint(originalPaint);

        // set element's id
        g.setCurrentGroupId(id);

        drawLabel(name,
                graphicInfo);
    }

    public void drawErrorEndEvent(String id,
                                  String name,
                                  GraphicInfo graphicInfo) {
        drawNoneEndEvent(id,
                name,
                graphicInfo);

        int imageX = (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4));
        int imageY = (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4));

        ERROR_THROW_IMAGE.drawIcon(imageX,
                imageY,
                ICON_PADDING,
                g);
    }

    public void drawErrorStartEvent(String id,
                                    GraphicInfo graphicInfo) {
        drawNoneStartEvent(id,
                graphicInfo);

        int imageX = (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4));
        int imageY = (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4));

        ERROR_THROW_IMAGE.drawIcon(imageX,
                imageY,
                ICON_PADDING,
                g);
    }

    public void drawCatchingEvent(String id,
                                  GraphicInfo graphicInfo,
                                  boolean isInterrupting,
                                  IconType icon,
                                  String eventType) {

        // event circles
        Ellipse2D outerCircle = new Ellipse2D.Double(graphicInfo.getX(),
                graphicInfo.getY(),
                graphicInfo.getWidth(),
                graphicInfo.getHeight());
        int innerCircleSize = 4;
        int innerCircleX = (int) graphicInfo.getX() + innerCircleSize;
        int innerCircleY = (int) graphicInfo.getY() + innerCircleSize;
        int innerCircleWidth = (int) graphicInfo.getWidth() - (2 * innerCircleSize);
        int innerCircleHeight = (int) graphicInfo.getHeight() - (2 * innerCircleSize);
        Ellipse2D innerCircle = new Ellipse2D.Double(innerCircleX,
                innerCircleY,
                innerCircleWidth,
                innerCircleHeight);

        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();
        g.setPaint(EVENT_COLOR);
        g.fill(outerCircle);

        g.setPaint(EVENT_BORDER_COLOR);
        if (!isInterrupting) {
            g.setStroke(NON_INTERRUPTING_EVENT_STROKE);
        }
        g.draw(outerCircle);
        g.setStroke(originalStroke);
        g.setPaint(originalPaint);
        g.draw(innerCircle);

        if (icon != null) {
            // calculate coordinates to center image
            int imageX = (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (icon.getWidth() / 2));
            int imageY = (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (icon.getHeight() / 2));
            if ("timer".equals(eventType)) {
                // move image one pixel to center timer image
                imageX++;
                imageY++;
            }
            icon.drawIcon(imageX,
                    imageY,
                    ICON_PADDING,
                    g);
        }

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawCatchingCompensateEvent(String id,
                                            String name,
                                            GraphicInfo graphicInfo,
                                            boolean isInterrupting) {
        drawCatchingCompensateEvent(id,
                graphicInfo,
                isInterrupting);
        drawLabel(name,
                graphicInfo);
    }

    public void drawCatchingCompensateEvent(String id,
                                            GraphicInfo graphicInfo,
                                            boolean isInterrupting) {

        drawCatchingEvent(id,
                graphicInfo,
                isInterrupting,
                COMPENSATE_CATCH_IMAGE,
                "compensate");
    }

    public void drawCatchingTimerEvent(String id,
                                       String name,
                                       GraphicInfo graphicInfo,
                                       boolean isInterrupting) {
        drawCatchingTimerEvent(id,
                graphicInfo,
                isInterrupting);
        drawLabel(name,
                graphicInfo);
    }

    public void drawCatchingTimerEvent(String id,
                                       GraphicInfo graphicInfo,
                                       boolean isInterrupting) {
        drawCatchingEvent(id,
                graphicInfo,
                isInterrupting,
                TIMER_IMAGE,
                "timer");
    }

    public void drawCatchingErrorEvent(String id,
                                       String name,
                                       GraphicInfo graphicInfo,
                                       boolean isInterrupting) {
        drawCatchingErrorEvent(id,
                graphicInfo,
                isInterrupting);
        drawLabel(name,
                graphicInfo);
    }

    public void drawCatchingErrorEvent(String id,
                                       GraphicInfo graphicInfo,
                                       boolean isInterrupting) {

        drawCatchingEvent(id,
                graphicInfo,
                isInterrupting,
                ERROR_CATCH_IMAGE,
                "error");
    }

    public void drawCatchingSignalEvent(String id,
                                        String name,
                                        GraphicInfo graphicInfo,
                                        boolean isInterrupting) {
        drawCatchingSignalEvent(id,
                graphicInfo,
                isInterrupting);
        drawLabel(name,
                graphicInfo);
    }

    public void drawCatchingSignalEvent(String id,
                                        GraphicInfo graphicInfo,
                                        boolean isInterrupting) {
        drawCatchingEvent(id,
                graphicInfo,
                isInterrupting,
                SIGNAL_CATCH_IMAGE,
                "signal");
    }

    public void drawCatchingMessageEvent(String id,
                                         GraphicInfo graphicInfo,
                                         boolean isInterrupting) {

        drawCatchingEvent(id,
                graphicInfo,
                isInterrupting,
                MESSAGE_CATCH_IMAGE,
                "message");
    }

    public void drawCatchingMessageEvent(String id,
                                         String name,
                                         GraphicInfo graphicInfo,
                                         boolean isInterrupting) {
        drawCatchingEvent(id,
                graphicInfo,
                isInterrupting,
                MESSAGE_CATCH_IMAGE,
                "message");

        drawLabel(name,
                graphicInfo);
    }

    public void drawThrowingCompensateEvent(String id,
                                            GraphicInfo graphicInfo) {
        drawCatchingEvent(id,
                graphicInfo,
                true,
                COMPENSATE_THROW_IMAGE,
                "compensate");
    }

    public void drawThrowingSignalEvent(String id,
                                        GraphicInfo graphicInfo) {
        drawCatchingEvent(id,
                graphicInfo,
                true,
                SIGNAL_THROW_IMAGE,
                "signal");
    }

    public void drawThrowingNoneEvent(String id,
                                      GraphicInfo graphicInfo) {
        drawCatchingEvent(id,
                graphicInfo,
                true,
                null,
                "none");
    }

    public void drawSequenceflow(int srcX,
                                 int srcY,
                                 int targetX,
                                 int targetY,
                                 boolean conditional) {
        drawSequenceflow(srcX,
                srcY,
                targetX,
                targetY,
                conditional,
                false);
    }

    public void drawSequenceflow(int srcX,
                                 int srcY,
                                 int targetX,
                                 int targetY,
                                 boolean conditional,
                                 boolean highLighted) {
        Paint originalPaint = g.getPaint();
        if (highLighted) {
            g.setPaint(HIGHLIGHT_COLOR);
        }

        Line2D.Double line = new Line2D.Double(srcX,
                srcY,
                targetX,
                targetY);
        g.draw(line);
        drawArrowHead(line);

        if (conditional) {
            drawConditionalSequenceFlowIndicator(line);
        }

        if (highLighted) {
            g.setPaint(originalPaint);
        }
    }

    public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, boolean highLighted, double scaleFactor) {
        this.drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted, scaleFactor);
    }

    public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        this.g.setPaint(CONNECTION_COLOR);
        if(connectionType.equals("association")) {
            this.g.setStroke(ASSOCIATION_STROKE);
        } else if(highLighted) {
            this.g.setPaint(HIGHLIGHT_COLOR);
            this.g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }

        for(int i = 1; i < xPoints.length; ++i) {
            Integer sourceX = Integer.valueOf(xPoints[i - 1]);
            Integer sourceY = Integer.valueOf(yPoints[i - 1]);
            Integer targetX = Integer.valueOf(xPoints[i]);
            Integer targetY = Integer.valueOf(yPoints[i]);
            Line2D.Double line = new Line2D.Double((double)sourceX.intValue(), (double)sourceY.intValue(), (double)targetX.intValue(), (double)targetY.intValue());
            this.g.draw(line);
        }

        Line2D.Double line;
        if(isDefault) {
            line = new Line2D.Double((double)xPoints[0], (double)yPoints[0], (double)xPoints[1], (double)yPoints[1]);
            this.drawDefaultSequenceFlowIndicator(line);
        }

        if(conditional) {
            line = new Line2D.Double((double)xPoints[0], (double)yPoints[0], (double)xPoints[1], (double)yPoints[1]);
            this.drawConditionalSequenceFlowIndicator(line);
        }

        if(associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
            line = new Line2D.Double((double)xPoints[xPoints.length - 2], (double)yPoints[xPoints.length - 2], (double)xPoints[xPoints.length - 1], (double)yPoints[xPoints.length - 1]);
            this.drawArrowHead(line);
        }

        if(associationDirection.equals(AssociationDirection.BOTH)) {
            line = new Line2D.Double((double)xPoints[1], (double)yPoints[1], (double)xPoints[0], (double)yPoints[0]);
            this.drawArrowHead(line);
        }

        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }

    public void drawAssociation(int[] xPoints,
                                int[] yPoints,
                                AssociationDirection associationDirection,
                                boolean highLighted) {
        boolean conditional = false;
        boolean isDefault = false;
        drawConnection(xPoints,
                yPoints,
                conditional,
                isDefault,
                "association",
                associationDirection,
                highLighted);
    }

    public void drawSequenceflow(int[] xPoints,
                                 int[] yPoints,
                                 boolean conditional,
                                 boolean isDefault,
                                 boolean highLighted) {
        drawConnection(xPoints,
                yPoints,
                conditional,
                isDefault,
                "sequenceFlow",
                AssociationDirection.ONE,
                highLighted);
    }

    public void drawConnection(int[] xPoints,
                               int[] yPoints,
                               boolean conditional,
                               boolean isDefault,
                               String connectionType,
                               AssociationDirection associationDirection,
                               boolean highLighted) {

        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();

        g.setPaint(CONNECTION_COLOR);
        if ("association".equals(connectionType)) {
            g.setStroke(ASSOCIATION_STROKE);
        } else if (highLighted) {
            g.setPaint(HIGHLIGHT_COLOR);
            g.setStroke(HIGHLIGHT_FLOW_STROKE);
        }

        for (int i = 1; i < xPoints.length; i++) {
            Integer sourceX = xPoints[i - 1];
            Integer sourceY = yPoints[i - 1];
            Integer targetX = xPoints[i];
            Integer targetY = yPoints[i];
            Line2D.Double line = new Line2D.Double(sourceX,
                    sourceY,
                    targetX,
                    targetY);
            g.draw(line);
        }

        if (isDefault) {
            Line2D.Double line = new Line2D.Double(xPoints[0],
                    yPoints[0],
                    xPoints[1],
                    yPoints[1]);
            drawDefaultSequenceFlowIndicator(line);
        }

        if (conditional) {
            Line2D.Double line = new Line2D.Double(xPoints[0],
                    yPoints[0],
                    xPoints[1],
                    yPoints[1]);
            drawConditionalSequenceFlowIndicator(line);
        }

        if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
            Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2],
                    yPoints[xPoints.length - 2],
                    xPoints[xPoints.length - 1],
                    yPoints[xPoints.length - 1]);
            drawArrowHead(line);
        }
        if (associationDirection.equals(AssociationDirection.BOTH)) {
            Line2D.Double line = new Line2D.Double(xPoints[1],
                    yPoints[1],
                    xPoints[0],
                    yPoints[0]);
            drawArrowHead(line);
        }
        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }

    public void drawSequenceflowWithoutArrow(int srcX,
                                             int srcY,
                                             int targetX,
                                             int targetY,
                                             boolean conditional) {
        drawSequenceflowWithoutArrow(srcX,
                srcY,
                targetX,
                targetY,
                conditional,
                false);
    }

    public void drawSequenceflowWithoutArrow(int srcX,
                                             int srcY,
                                             int targetX,
                                             int targetY,
                                             boolean conditional,
                                             boolean highLighted) {
        Paint originalPaint = g.getPaint();
        if (highLighted) {
            g.setPaint(HIGHLIGHT_COLOR);
        }

        Line2D.Double line = new Line2D.Double(srcX,
                srcY,
                targetX,
                targetY);
        g.draw(line);

        if (conditional) {
            drawConditionalSequenceFlowIndicator(line);
        }

        if (highLighted) {
            g.setPaint(originalPaint);
        }
    }

    public void drawArrowHead(Line2D.Double line) {
        int doubleArrowWidth = (int) (2 * ARROW_WIDTH);
        if (doubleArrowWidth == 0) {
            doubleArrowWidth = 2;
        }
        Polygon arrowHead = new Polygon();
        arrowHead.addPoint(0,
                0);
        int arrowHeadPoint = (int) (-ARROW_WIDTH);
        if (arrowHeadPoint == 0) {
            arrowHeadPoint = -1;
        }
        arrowHead.addPoint(arrowHeadPoint,
                -doubleArrowWidth);
        arrowHeadPoint = (int) (ARROW_WIDTH);
        if (arrowHeadPoint == 0) {
            arrowHeadPoint = 1;
        }
        arrowHead.addPoint(arrowHeadPoint,
                -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 = g.getTransform();
        g.setTransform(transformation);
        g.fill(arrowHead);
        g.setTransform(originalTransformation);
    }

    public void drawDefaultSequenceFlowIndicator(Line2D.Double line) {
        double length = DEFAULT_INDICATOR_WIDTH;
        double halfOfLength = length / 2;
        double f = 8;
        Line2D.Double defaultIndicator = new Line2D.Double(-halfOfLength,
                0,
                halfOfLength,
                0);

        double angle = Math.atan2(line.y2 - line.y1,
                line.x2 - line.x1);
        double dx = f * Math.cos(angle);
        double dy = f * Math.sin(angle);
        double x1 = line.x1 + dx;
        double y1 = line.y1 + dy;

        AffineTransform transformation = new AffineTransform();
        transformation.setToIdentity();
        transformation.translate(x1,
                y1);
        transformation.rotate((angle - 3 * Math.PI / 4));

        AffineTransform originalTransformation = g.getTransform();
        g.setTransform(transformation);
        g.draw(defaultIndicator);

        g.setTransform(originalTransformation);
    }

    public void drawConditionalSequenceFlowIndicator(Line2D.Double line) {
        int horizontal = (int) (CONDITIONAL_INDICATOR_WIDTH * 0.7);
        int halfOfHorizontal = horizontal / 2;
        int halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2;

        Polygon conditionalIndicator = new Polygon();
        conditionalIndicator.addPoint(0,
                0);
        conditionalIndicator.addPoint(-halfOfHorizontal,
                halfOfVertical);
        conditionalIndicator.addPoint(0,
                CONDITIONAL_INDICATOR_WIDTH);
        conditionalIndicator.addPoint(halfOfHorizontal,
                halfOfVertical);

        AffineTransform transformation = new AffineTransform();
        transformation.setToIdentity();
        double angle = Math.atan2(line.y2 - line.y1,
                line.x2 - line.x1);
        transformation.translate(line.x1,
                line.y1);
        transformation.rotate((angle - Math.PI / 2d));

        AffineTransform originalTransformation = g.getTransform();
        g.setTransform(transformation);
        g.draw(conditionalIndicator);

        Paint originalPaint = g.getPaint();
        g.setPaint(CONDITIONAL_INDICATOR_COLOR);
        g.fill(conditionalIndicator);

        g.setPaint(originalPaint);
        g.setTransform(originalTransformation);
    }

    public void drawTask(TaskIconType icon,
                         String id,
                         String name,
                         GraphicInfo graphicInfo) {
        drawTask(id,
                name,
                graphicInfo);

        icon.drawIcon((int) graphicInfo.getX(),
                (int) graphicInfo.getY(),
                ICON_PADDING,
                g);
    }

    public void drawTask(String id,
                         String name,
                         GraphicInfo graphicInfo) {
        drawTask(id,
                name,
                graphicInfo,
                false);
    }

    public void drawPoolOrLane(String id,
                               String name,
                               GraphicInfo graphicInfo) {
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();
        g.drawRect(x,
                y,
                width,
                height);

        // Add the name as text, vertical
        if (name != null && name.length() > 0) {
            // Include some padding
            int availableTextSpace = height - 6;

            // Create rotation for derived font
            AffineTransform transformation = new AffineTransform();
            transformation.setToIdentity();
            transformation.rotate(270 * Math.PI / 180);

            Font currentFont = g.getFont();
            Font theDerivedFont = currentFont.deriveFont(transformation);
            g.setFont(theDerivedFont);

            String truncated = fitTextToWidth(name,
                    availableTextSpace);
            int realWidth = fontMetrics.stringWidth(truncated);

            g.drawString(truncated,
                    x + 2 + fontMetrics.getHeight(),
                    3 + y + availableTextSpace - (availableTextSpace - realWidth) / 2);
            g.setFont(currentFont);
        }

        // set element's id
        g.setCurrentGroupId(id);
    }

    protected void drawTask(String id,
                            String name,
                            GraphicInfo graphicInfo,
                            boolean thickBorder) {
        Paint originalPaint = g.getPaint();
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();
        // Create a new gradient paint for every task box, gradient depends on x and y and is not relative
        g.setPaint(TASK_BOX_COLOR);

        int arcR = 6;
        if (thickBorder) {
            arcR = 3;
        }

        // shape
        RoundRectangle2D rect = new RoundRectangle2D.Double(x,
                y,
                width,
                height,
                arcR,
                arcR);
        g.fill(rect);
        g.setPaint(TASK_BORDER_COLOR);

        if (thickBorder) {
            Stroke originalStroke = g.getStroke();
            g.setStroke(THICK_TASK_BORDER_STROKE);
            g.draw(rect);
            g.setStroke(originalStroke);
        } else {
            g.draw(rect);
        }

        g.setPaint(originalPaint);
        // text
        if (name != null && name.length() > 0) {
            int boxWidth = width - (2 * TEXT_PADDING);
            int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
            int boxX = x + width / 2 - boxWidth / 2;
            int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2;

            drawMultilineCentredText(name,
                    boxX,
                    boxY,
                    boxWidth,
                    boxHeight);


        }

        // set element's id
        g.setCurrentGroupId(id);
    }

    protected void drawMultilineCentredText(String text,
                                            int x,
                                            int y,
                                            int boxWidth,
                                            int boxHeight) {
        drawMultilineText(text,
                x,
                y,
                boxWidth,
                boxHeight,
                true);
    }

    protected void drawMultilineAnnotationText(String text,
                                               int x,
                                               int y,
                                               int boxWidth,
                                               int boxHeight) {
        drawMultilineText(text,
                x,
                y,
                boxWidth,
                boxHeight,
                false);
    }

    protected void drawMultilineText(String text,
                                     int x,
                                     int y,
                                     int boxWidth,
                                     int boxHeight,
                                     boolean centered) {
        // Create an attributed string based in input text
        AttributedString attributedString = new AttributedString(text);
        attributedString.addAttribute(TextAttribute.FONT,
                g.getFont());
        attributedString.addAttribute(TextAttribute.FOREGROUND,
                Color.black);

        AttributedCharacterIterator characterIterator = attributedString.getIterator();

        int currentHeight = 0;
        // Prepare a list of lines of text we'll be drawing
        List<TextLayout> layouts = new ArrayList<TextLayout>();
        String lastLine = null;

        LineBreakMeasurer measurer = new LineBreakMeasurer(characterIterator,
                g.getFontRenderContext());

        TextLayout layout = null;
       // while (measurer.getPosition() < characterIterator.getEndIndex() && currentHeight <= boxHeight) {
            while (measurer.getPosition() < characterIterator.getEndIndex()) {

            int previousPosition = measurer.getPosition();

            // Request next layout
            layout = measurer.nextLayout(boxWidth);

            int height = ((Float) (layout.getDescent() + layout.getAscent() + layout.getLeading())).intValue();
            if (currentHeight + height > boxHeight) {
                // The line we're about to add should NOT be added anymore, append three dots to previous one instead
                // to indicate more text is truncated
                if (!layouts.isEmpty()) {
                    layouts.remove(layouts.size() - 1);

                    if (lastLine.length() >= 4) {
                        lastLine = lastLine.substring(0,
                                lastLine.length() - 4) + "...";
                    }
                    layouts.add(new TextLayout(lastLine,
                            g.getFont(),
                            g.getFontRenderContext()));
                } else {
                    // at least, draw one line
                    // even if text does not fit
                    // in order to avoid empty box
                    layouts.add(layout);
                    currentHeight += height;
                }
                break;
            } else {
                layouts.add(layout);
                lastLine = text.substring(previousPosition,
                        measurer.getPosition());
                currentHeight += height;
            }
        }
        int currentY = y + (centered ? ((boxHeight - currentHeight) / 2) : 0);
        int currentX = 0;

        // Actually draw the lines
        for (TextLayout textLayout : layouts) {

            currentY += textLayout.getAscent();
            currentX = x + (centered ? ((boxWidth - ((Double) textLayout.getBounds().getWidth()).intValue()) / 2) : 0);

            textLayout.draw(g,
                    currentX,
                    currentY);
            currentY += textLayout.getDescent() + textLayout.getLeading();
        }
    }

    protected String fitTextToWidth(String original,
                                    int width) {
        String text = original;

        // remove length for "..."
        int maxWidth = width - 10;

        while (fontMetrics.stringWidth(text + "...") > maxWidth && text.length() > 0) {
            text = text.substring(0,
                    text.length() - 1);
        }

        if (!text.equals(original)) {
            text = text + "...";
        }

        return text;
    }

    public void drawUserTask(String id,
                             String name,
                             GraphicInfo graphicInfo) {
        drawTask(USERTASK_IMAGE,
                id,
                name,
                graphicInfo);
    }

    public void drawScriptTask(String id,
                               String name,
                               GraphicInfo graphicInfo) {
        drawTask(SCRIPTTASK_IMAGE,
                id,
                name,
                graphicInfo);
    }

    public void drawServiceTask(String id,
                                String name,
                                GraphicInfo graphicInfo) {
        drawTask(SERVICETASK_IMAGE,
                id,
                name,
                graphicInfo);
    }

    public void drawReceiveTask(String id,
                                String name,
                                GraphicInfo graphicInfo) {
        drawTask(RECEIVETASK_IMAGE,
                id,
                name,
                graphicInfo);
    }

    public void drawSendTask(String id,
                             String name,
                             GraphicInfo graphicInfo) {
        drawTask(SENDTASK_IMAGE,
                id,
                name,
                graphicInfo);
    }

    public void drawManualTask(String id,
                               String name,
                               GraphicInfo graphicInfo) {
        drawTask(MANUALTASK_IMAGE,
                id,
                name,
                graphicInfo);
    }

    public void drawBusinessRuleTask(String id,
                                     String name,
                                     GraphicInfo graphicInfo) {
        drawTask(BUSINESS_RULE_TASK_IMAGE,
                id,
                name,
                graphicInfo);
    }

    public void drawExpandedSubProcess(String id,
                                       String name,
                                       GraphicInfo graphicInfo,
                                       Class<?> type) {
        RoundRectangle2D rect = new RoundRectangle2D.Double(graphicInfo.getX(),
                graphicInfo.getY(),
                graphicInfo.getWidth(),
                graphicInfo.getHeight(),
                8,
                8);

        if (type.equals(EventSubProcess.class)) {
            Stroke originalStroke = g.getStroke();
            g.setStroke(EVENT_SUBPROCESS_STROKE);
            g.draw(rect);
            g.setStroke(originalStroke);
        } else if (type.equals(Transaction.class)) {
            RoundRectangle2D outerRect = new RoundRectangle2D.Double(graphicInfo.getX()-3,
                    graphicInfo.getY()-3,
                    graphicInfo.getWidth()+6,
                    graphicInfo.getHeight()+6,
                    8,
                    8);

            Paint originalPaint = g.getPaint();
            g.setPaint(SUBPROCESS_BOX_COLOR);
            g.fill(outerRect);
            g.setPaint(SUBPROCESS_BORDER_COLOR);
            g.draw(outerRect);
            g.setPaint(SUBPROCESS_BOX_COLOR);
            g.fill(rect);
            g.setPaint(SUBPROCESS_BORDER_COLOR);
            g.draw(rect);
            g.setPaint(originalPaint);
        } else {
            Paint originalPaint = g.getPaint();
            g.setPaint(SUBPROCESS_BOX_COLOR);
            g.fill(rect);
            g.setPaint(SUBPROCESS_BORDER_COLOR);
            g.draw(rect);
            g.setPaint(originalPaint);
        }

        if (name != null && !name.isEmpty()) {
            String text = fitTextToWidth(name,
                    (int) graphicInfo.getWidth());
            g.drawString(text,
                    (int) graphicInfo.getX() + 10,
                    (int) graphicInfo.getY() + 15);
        }

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawCollapsedSubProcess(String id,
                                        String name,
                                        GraphicInfo graphicInfo,
                                        Boolean isTriggeredByEvent) {
        drawCollapsedTask(id,
                name,
                graphicInfo,
                false);
    }

    public void drawCollapsedCallActivity(String id,
                                          String name,
                                          GraphicInfo graphicInfo) {
        drawCollapsedTask(id,
                name,
                graphicInfo,
                true);
    }

    protected void drawCollapsedTask(String id,
                                     String name,
                                     GraphicInfo graphicInfo,
                                     boolean thickBorder) {
        // The collapsed marker is now visualized separately
        drawTask(id,
                name,
                graphicInfo,
                thickBorder);
    }

    public void drawCollapsedMarker(int x,
                                    int y,
                                    int width,
                                    int height) {
        // rectangle
        int rectangleWidth = MARKER_WIDTH;
        int rectangleHeight = MARKER_WIDTH;
        Rectangle rect = new Rectangle(x + (width - rectangleWidth) / 2,
                y + height - rectangleHeight - 3,
                rectangleWidth,
                rectangleHeight);
        g.draw(rect);

        // plus inside rectangle
        Line2D.Double line = new Line2D.Double(rect.getCenterX(),
                rect.getY() + 2,
                rect.getCenterX(),
                rect.getMaxY() - 2);
        g.draw(line);
        line = new Line2D.Double(rect.getMinX() + 2,
                rect.getCenterY(),
                rect.getMaxX() - 2,
                rect.getCenterY());
        g.draw(line);
    }

    public void drawActivityMarkers(int x,
                                    int y,
                                    int width,
                                    int height,
                                    boolean multiInstanceSequential,
                                    boolean multiInstanceParallel,
                                    boolean collapsed) {
        if (collapsed) {
            if (!multiInstanceSequential && !multiInstanceParallel) {
                drawCollapsedMarker(x,
                        y,
                        width,
                        height);
            } else {
                drawCollapsedMarker(x - MARKER_WIDTH / 2 - 2,
                        y,
                        width,
                        height);
                if (multiInstanceSequential) {
                    drawMultiInstanceMarker(true,
                            x + MARKER_WIDTH / 2 + 2,
                            y,
                            width,
                            height);
                } else {
                    drawMultiInstanceMarker(false,
                            x + MARKER_WIDTH / 2 + 2,
                            y,
                            width,
                            height);
                }
            }
        } else {
            if (multiInstanceSequential) {
                drawMultiInstanceMarker(true,
                        x,
                        y,
                        width,
                        height);
            } else if (multiInstanceParallel) {
                drawMultiInstanceMarker(false,
                        x,
                        y,
                        width,
                        height);
            }
        }
    }

    public void drawGateway(GraphicInfo graphicInfo) {
        Polygon rhombus = new Polygon();
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();

        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);
        g.draw(rhombus);
    }

    public void drawParallelGateway(String id,
                                    GraphicInfo graphicInfo) {
        // rhombus
        drawGateway(graphicInfo);
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();

        // plus inside rhombus
        Stroke orginalStroke = g.getStroke();
        g.setStroke(GATEWAY_TYPE_STROKE);
        Line2D.Double line = new Line2D.Double(x + 10,
                y + height / 2,
                x + width - 10,
                y + height / 2); // horizontal
        g.draw(line);
        line = new Line2D.Double(x + width / 2,
                y + height - 10,
                x + width / 2,
                y + 10); // vertical
        g.draw(line);
        g.setStroke(orginalStroke);

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawExclusiveGateway(String id,
                                     GraphicInfo graphicInfo) {
        // rhombus
        drawGateway(graphicInfo);
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();

        int quarterWidth = width / 4;
        int quarterHeight = height / 4;

        // X inside rhombus
        Stroke orginalStroke = g.getStroke();
        g.setStroke(GATEWAY_TYPE_STROKE);
        Line2D.Double line = new Line2D.Double(x + quarterWidth + 3,
                y + quarterHeight + 3,
                x + 3 * quarterWidth - 3,
                y + 3 * quarterHeight - 3);
        g.draw(line);
        line = new Line2D.Double(x + quarterWidth + 3,
                y + 3 * quarterHeight - 3,
                x + 3 * quarterWidth - 3,
                y + quarterHeight + 3);
        g.draw(line);
        g.setStroke(orginalStroke);

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawInclusiveGateway(String id,
                                     GraphicInfo graphicInfo) {
        // rhombus
        drawGateway(graphicInfo);
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();

        int diameter = width / 2;

        // circle inside rhombus
        Stroke orginalStroke = g.getStroke();
        g.setStroke(GATEWAY_TYPE_STROKE);
        Ellipse2D.Double circle = new Ellipse2D.Double(((width - diameter) / 2) + x,
                ((height - diameter) / 2) + y,
                diameter,
                diameter);
        g.draw(circle);
        g.setStroke(orginalStroke);

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawEventBasedGateway(String id,
                                      GraphicInfo graphicInfo) {
        // rhombus
        drawGateway(graphicInfo);

        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();

        double scale = .6;

        GraphicInfo eventInfo = new GraphicInfo();
        eventInfo.setX(x + width * (1 - scale) / 2);
        eventInfo.setY(y + height * (1 - scale) / 2);
        eventInfo.setWidth(width * scale);
        eventInfo.setHeight(height * scale);
        drawCatchingEvent(null,
                eventInfo,
                true,
                null,
                "eventGateway");

        double r = width / 6.;

        // create pentagon (coords with respect to center)
        int topX = (int) (.95 * r); // top right corner
        int topY = (int) (-.31 * r);
        int bottomX = (int) (.59 * r); // bottom right corner
        int bottomY = (int) (.81 * r);

        int[] xPoints = new int[]{0, topX, bottomX, -bottomX, -topX};
        int[] yPoints = new int[]{-(int) r, topY, bottomY, bottomY, topY};
        Polygon pentagon = new Polygon(xPoints,
                yPoints,
                5);
        pentagon.translate(x + width / 2,
                y + width / 2);

        // draw
        g.drawPolygon(pentagon);

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawMultiInstanceMarker(boolean sequential,
                                        int x,
                                        int y,
                                        int width,
                                        int height) {
        int rectangleWidth = MARKER_WIDTH;
        int rectangleHeight = MARKER_WIDTH;
        int lineX = x + (width - rectangleWidth) / 2;
        int lineY = y + height - rectangleHeight - 3;

        Stroke orginalStroke = g.getStroke();
        g.setStroke(MULTI_INSTANCE_STROKE);

        if (sequential) {
            g.draw(new Line2D.Double(lineX,
                    lineY,
                    lineX + rectangleWidth,
                    lineY));
            g.draw(new Line2D.Double(lineX,
                    lineY + rectangleHeight / 2,
                    lineX + rectangleWidth,
                    lineY + rectangleHeight / 2));
            g.draw(new Line2D.Double(lineX,
                    lineY + rectangleHeight,
                    lineX + rectangleWidth,
                    lineY + rectangleHeight));
        } else {
            g.draw(new Line2D.Double(lineX,
                    lineY,
                    lineX,
                    lineY + rectangleHeight));
            g.draw(new Line2D.Double(lineX + rectangleWidth / 2,
                    lineY,
                    lineX + rectangleWidth / 2,
                    lineY + rectangleHeight));
            g.draw(new Line2D.Double(lineX + rectangleWidth,
                    lineY,
                    lineX + rectangleWidth,
                    lineY + rectangleHeight));
        }

        g.setStroke(orginalStroke);
    }

    public void drawHighLight(int x,
                              int y,
                              int width,
                              int height) {
        Paint originalPaint = g.getPaint();
        Stroke originalStroke = g.getStroke();

        g.setPaint(HIGHLIGHT_COLOR);
        g.setStroke(THICK_TASK_BORDER_STROKE);

        RoundRectangle2D rect = new RoundRectangle2D.Double(x,
                y,
                width,
                height,
                20,
                20);
        g.draw(rect);

        g.setPaint(originalPaint);
        g.setStroke(originalStroke);
    }

    public void drawHighLight(int x, int y, int width, int height, Color color) {
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        this.g.setPaint(color);
        this.g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double((double)x, (double)y, (double)width, (double)height, 20.0D, 20.0D);
        this.g.draw(rect);
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }

    public void drawHighLightEvent(int x, int y, int width, int height, Color color) {
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        Ellipse2D.Double circle = new Ellipse2D.Double(x, y, width, height);
        this.g.setStroke(THICK_TASK_BORDER_STROKE);
        this.g.setPaint(color);
        this.g.draw(circle);
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }

    public void drawHighLightGateway(int x, int y, int width, int height, Color color){
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        Polygon rhombus = new Polygon();
        this.g.setPaint(color);
        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);
        this.g.draw(rhombus);
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }

    public void drawHighLightTask(int x, int y, int width, int height, Color color) {
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        this.g.setPaint(color);
        this.g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double((double)x, (double)y, (double)width, (double)height, 10.0D, 10.0D);
        this.g.draw(rect);
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }

    public void drawHighLightSubProcess(int x, int y, int width, int height, Color color) {
        Paint originalPaint = this.g.getPaint();
        Stroke originalStroke = this.g.getStroke();
        this.g.setPaint(color);
        this.g.setStroke(THICK_TASK_BORDER_STROKE);
        RoundRectangle2D rect = new RoundRectangle2D.Double(x + 1, y + 1,width - 2, height - 2, 5.0D, 5.0D);
        this.g.draw(rect);
        this.g.setPaint(originalPaint);
        this.g.setStroke(originalStroke);
    }

    public void drawTextAnnotation(String id,
                                   String text,
                                   GraphicInfo graphicInfo) {
        int x = (int) graphicInfo.getX();
        int y = (int) graphicInfo.getY();
        int width = (int) graphicInfo.getWidth();
        int height = (int) graphicInfo.getHeight();

        Font originalFont = g.getFont();
        Stroke originalStroke = g.getStroke();

        g.setFont(ANNOTATION_FONT);

        Path2D path = new Path2D.Double();
        x += .5;
        int lineLength = 18;
        path.moveTo(x + lineLength,
                y);
        path.lineTo(x,
                y);
        path.lineTo(x,
                y + height);
        path.lineTo(x + lineLength,
                y + height);

        path.lineTo(x + lineLength,
                y + height - 1);
        path.lineTo(x + 1,
                y + height - 1);
        path.lineTo(x + 1,
                y + 1);
        path.lineTo(x + lineLength,
                y + 1);
        path.closePath();

        g.draw(path);

        int boxWidth = width - (2 * ANNOTATION_TEXT_PADDING);
        int boxHeight = height - (2 * ANNOTATION_TEXT_PADDING);
        int boxX = x + width / 2 - boxWidth / 2;
        int boxY = y + height / 2 - boxHeight / 2;

        if (text != null && !text.isEmpty()) {
            drawMultilineAnnotationText(text,
                    boxX,
                    boxY,
                    boxWidth,
                    boxHeight);
        }

        // restore originals
        g.setFont(originalFont);
        g.setStroke(originalStroke);

        // set element's id
        g.setCurrentGroupId(id);
    }

    public void drawLabel(String text,
                          GraphicInfo graphicInfo) {
        drawLabel(text,
                graphicInfo,
                true);
    }

    public void drawLabel(String text,
                          GraphicInfo graphicInfo,
                          boolean centered) {
        float interline = 1.0f;

        // text
        if (text != null && text.length() > 0) {
            Paint originalPaint = g.getPaint();
            Font originalFont = g.getFont();

            g.setPaint(SUBPROCESS_BORDER_COLOR);
            g.setFont(LABEL_FONT);

            int wrapWidth = 100;
            int textY = (int) graphicInfo.getY();

            // TODO: use drawMultilineText()
            AttributedString as = new AttributedString(text);
            as.addAttribute(TextAttribute.FOREGROUND,
                    g.getPaint());
            as.addAttribute(TextAttribute.FONT,
                    g.getFont());
            AttributedCharacterIterator aci = as.getIterator();
            FontRenderContext frc = new FontRenderContext(null,
                    true,
                    false);
            LineBreakMeasurer lbm = new LineBreakMeasurer(aci,
                    frc);

            while (lbm.getPosition() < text.length()) {
                TextLayout tl = lbm.nextLayout(wrapWidth);
                textY += tl.getAscent();
                Rectangle2D bb = tl.getBounds();
                double tX = graphicInfo.getX();
                if (centered) {
                    tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
                }
                tl.draw(g,
                        (float) tX,
                        textY);
                textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
            }

            // restore originals
            g.setFont(originalFont);
            g.setPaint(originalPaint);
        }
    }

    /**
     * This method makes coordinates of connection flow better.
     * @param sourceShapeType
     * @param targetShapeType
     * @param sourceGraphicInfo
     * @param targetGraphicInfo
     * @param graphicInfoList
     */
    public List<GraphicInfo> connectionPerfectionizer(SHAPE_TYPE sourceShapeType,
                                                      SHAPE_TYPE targetShapeType,
                                                      GraphicInfo sourceGraphicInfo,
                                                      GraphicInfo targetGraphicInfo,
                                                      List<GraphicInfo> graphicInfoList) {
        Shape shapeFirst = createShape(sourceShapeType,
                sourceGraphicInfo);
        Shape shapeLast = createShape(targetShapeType,
                targetGraphicInfo);

        if (graphicInfoList != null && graphicInfoList.size() > 0) {
            GraphicInfo graphicInfoFirst = graphicInfoList.get(0);
            GraphicInfo graphicInfoLast = graphicInfoList.get(graphicInfoList.size() - 1);
            if (shapeFirst != null) {
                graphicInfoFirst.setX(shapeFirst.getBounds2D().getCenterX());
                graphicInfoFirst.setY(shapeFirst.getBounds2D().getCenterY());
            }
            if (shapeLast != null) {
                graphicInfoLast.setX(shapeLast.getBounds2D().getCenterX());
                graphicInfoLast.setY(shapeLast.getBounds2D().getCenterY());
            }

            Point p = null;

            if (shapeFirst != null) {
                Line2D.Double lineFirst = new Line2D.Double(graphicInfoFirst.getX(),
                        graphicInfoFirst.getY(),
                        graphicInfoList.get(1).getX(),
                        graphicInfoList.get(1).getY());
                p = getIntersection(shapeFirst,
                        lineFirst);
                if (p != null) {
                    graphicInfoFirst.setX(p.getX());
                    graphicInfoFirst.setY(p.getY());
                }
            }

            if (shapeLast != null) {
                Line2D.Double lineLast = new Line2D.Double(graphicInfoLast.getX(),
                        graphicInfoLast.getY(),
                        graphicInfoList.get(graphicInfoList.size() - 2).getX(),
                        graphicInfoList.get(graphicInfoList.size() - 2).getY());
                p = getIntersection(shapeLast,
                        lineLast);
                if (p != null) {
                    graphicInfoLast.setX(p.getX());
                    graphicInfoLast.setY(p.getY());
                }
            }
        }

        return graphicInfoList;
    }

    /**
     * This method creates shape by type and coordinates.
     * @param shapeType
     * @param graphicInfo
     * @return Shape
     */
    private static Shape createShape(SHAPE_TYPE shapeType,
                                     GraphicInfo graphicInfo) {
        if (SHAPE_TYPE.Rectangle.equals(shapeType)) {
            // source is rectangle
            return new Rectangle2D.Double(graphicInfo.getX(),
                    graphicInfo.getY(),
                    graphicInfo.getWidth(),
                    graphicInfo.getHeight());
        } else if (SHAPE_TYPE.Rhombus.equals(shapeType)) {
            // source is rhombus
            Path2D.Double rhombus = new Path2D.Double();
            rhombus.moveTo(graphicInfo.getX(),
                    graphicInfo.getY() + graphicInfo.getHeight() / 2);
            rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2,
                    graphicInfo.getY() + graphicInfo.getHeight());
            rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth(),
                    graphicInfo.getY() + graphicInfo.getHeight() / 2);
            rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2,
                    graphicInfo.getY());
            rhombus.lineTo(graphicInfo.getX(),
                    graphicInfo.getY() + graphicInfo.getHeight() / 2);
            rhombus.closePath();
            return rhombus;
        } else if (SHAPE_TYPE.Ellipse.equals(shapeType)) {
            // source is ellipse
            return new Ellipse2D.Double(graphicInfo.getX(),
                    graphicInfo.getY(),
                    graphicInfo.getWidth(),
                    graphicInfo.getHeight());
        }
        // unknown source element, just do not correct coordinates
        return null;
    }

    /**
     * This method returns intersection point of shape border and line.
     * @param shape
     * @param line
     * @return Point
     */
    private static Point getIntersection(Shape shape,
                                         Line2D.Double line) {
        if (shape instanceof Ellipse2D) {
            return getEllipseIntersection(shape,
                    line);
        } else if (shape instanceof Rectangle2D || shape instanceof Path2D) {
            return getShapeIntersection(shape,
                    line);
        } else {
            // something strange
            return null;
        }
    }

    /**
     * This method calculates ellipse intersection with line
     * @param shape Bounds of this shape used to calculate parameters of inscribed into this bounds ellipse.
     * @param line
     * @return Intersection point
     */
    private static Point getEllipseIntersection(Shape shape,
                                                Line2D.Double line) {
        double angle = Math.atan2(line.y2 - line.y1,
                line.x2 - line.x1);
        double x = shape.getBounds2D().getWidth() / 2 * Math.cos(angle) + shape.getBounds2D().getCenterX();
        double y = shape.getBounds2D().getHeight() / 2 * Math.sin(angle) + shape.getBounds2D().getCenterY();
        Point p = new Point();
        p.setLocation(x,
                y);
        return p;
    }

    /**
     * This method calculates shape intersection with line.
     * @param shape
     * @param line
     * @return Intersection point
     */
    private static Point getShapeIntersection(Shape shape,
                                              Line2D.Double line) {
        PathIterator it = shape.getPathIterator(null);
        double[] coords = new double[6];
        double[] pos = new double[2];
        Line2D.Double l = new Line2D.Double();
        while (!it.isDone()) {
            int type = it.currentSegment(coords);
            switch (type) {
                case PathIterator.SEG_MOVETO:
                    pos[0] = coords[0];
                    pos[1] = coords[1];
                    break;
                case PathIterator.SEG_LINETO:
                    l = new Line2D.Double(pos[0],
                            pos[1],
                            coords[0],
                            coords[1]);
                    if (line.intersectsLine(l)) {
                        return getLinesIntersection(line,
                                l);
                    }
                    pos[0] = coords[0];
                    pos[1] = coords[1];
                    break;
                case PathIterator.SEG_CLOSE:
                    break;
                default:
                    // whatever
            }
            it.next();
        }
        return null;
    }

    /**
     * This method calculates intersections of two lines.
     * @param a Line 1
     * @param b Line 2
     * @return Intersection point
     */
    private static Point getLinesIntersection(Line2D a,
                                              Line2D b) {
        double d = (a.getX1() - a.getX2()) * (b.getY2() - b.getY1()) - (a.getY1() - a.getY2()) * (b.getX2() - b.getX1());
        double da = (a.getX1() - b.getX1()) * (b.getY2() - b.getY1()) - (a.getY1() - b.getY1()) * (b.getX2() - b.getX1());
        double ta = da / d;
        Point p = new Point();
        p.setLocation(a.getX1() + ta * (a.getX2() - a.getX1()),
                a.getY1() + ta * (a.getY2() - a.getY1()));
        return p;
    }

    public InputStream generateImage(String imageType) {
        if(this.closed) {
            throw new ActivitiImageException("ProcessDiagramGenerator already closed");
        } else {
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            try {
                ImageIO.write(this.processDiagram, imageType, out);
            } catch (IOException var11) {
                throw new ActivitiImageException("Error while generating process image", var11);
            } finally {
                try {
                    if(out != null) {
                        out.close();
                    }
                } catch (IOException var10) {
                    ;
                }

            }

            return new ByteArrayInputStream(out.toByteArray());
        }
    }
}


  1. 获取动态流程图
@RequestMapping(value = "/getFlowImgByInstanceId")
    @ApiOperation(value = "获取流程实例跟踪", httpMethod = "POST" ,notes = "获取流程实例跟踪")
    public void getFlowImgByInstanceId(@RequestBody Map<String, Object> map, HttpServletResponse response) {
	// processInstanceId 流程实例id
    processImageService.getFlowImgByInstanceId(map.get("processInstanceId").toString(), response);
    }
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;

/**
 * 流程图servic
 */
public interface ProcessImageService {


    /**
     * 根据流程实例Id获取流程图
     *
     * @param processInstanceId 流程实例id
     * @throws Exception exception
     */
    void getFlowImgByInstanceId(String processInstanceId,HttpServletResponse response);

}
import com.gxkj.common.modular.activiti.config.DefaultProcessDiagramGenerator;
import com.gxkj.common.modular.activiti.service.ProcessImageService;
import lombok.extern.slf4j.Slf4j;

import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.runtime.Execution;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import sun.misc.BASE64Encoder;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程图service实现类
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class ProcessImageServiceImpl implements ProcessImageService {

    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private HistoryService historyService;

    /**
     * 根据流程实例Id获取流程图
     *
     * @param processInstanceId 流程实例id
     * @throws Exception exception
     */
    @Override
    public void getFlowImgByInstanceId(String processInstanceId, HttpServletResponse response) {
        InputStream imageStream = null;
        try {
            //if (StringUtils.isEmpty(processInstanceId)) {
            //    return null;
            //}
            // 获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService
                    .createHistoricProcessInstanceQuery()
                    .processInstanceId(processInstanceId).singleResult();
            // 获取流程中已经执行的节点,按照执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstances = historyService
                    .createHistoricActivityInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .orderByHistoricActivityInstanceId()
                    .asc().list();
            // 高亮已经执行流程节点ID集合
            List<String> highLightedActivitiIds = new ArrayList<>();
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                // 用默认颜色
                highLightedActivitiIds.add(historicActivityInstance.getActivityId());
            }
            // 正在执行的节点
            List<Execution> runTaskList = runtimeService.createExecutionQuery()
                    .processInstanceId(processInstanceId)
                    .list();
            List<String> runningActivityIdList = new ArrayList<>();
            for (Execution execution : runTaskList) {
                if (!StringUtils.isEmpty(execution.getActivityId())) {
                    runningActivityIdList.add(execution.getActivityId());
                }
            }
            List<String> currIds = historicActivityInstances.stream()
                    .filter(item -> item.getEndTime() != null)
                    .map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());

            // 获得流程引擎配置
            ProcessEngineConfiguration processEngineConfiguration = processEngine.getProcessEngineConfiguration();

            BpmnModel bpmnModel = repositoryService
                    .getBpmnModel(historicProcessInstance.getProcessDefinitionId());
            // 高亮流程已发生流转的线id集合
            List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
            //
            imageStream = new DefaultProcessDiagramGenerator().generateDiagram(
                    bpmnModel,
                    "png",
                    highLightedActivitiIds,//所有活动过的节点,包括当前在激活状态下的节点
                    currIds,//当前为激活状态下的节点
                    highLightedFlowIds,//活动过的线
                    "宋体",
                    "宋体",
                    "宋体",
                    processEngineConfiguration.getClassLoader(),
                    1.0,
                    runningActivityIdList);
       
            OutputStream out = null;
            out = response.getOutputStream();
            int len = 0;
            byte[] b = new byte[1024];
            while ((len = imageStream.read(b)) != -1) {
                out.write(b, 0, len);
            }
            out.flush();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (imageStream != null) {
                try {
                    imageStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }


    ///**
    // *  获取已经流转的线
    // *  @param bpmnModel
    // * @param historicActivityInstances
    // * @return
    // */
    //private static List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
    //    // 高亮流程已发生流转的线id集合
    //    List<String> highLightedFlowIds = new ArrayList<>();
    //    // 全部活动节点
    //    List<FlowNode> historicActivityNodes = new ArrayList<>();
    //    // 已完成的历史活动节点
    //    List<HistoricActivityInstance> finishedActivityInstances = new ArrayList<>();
    //
    //    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
    //        FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
    //        historicActivityNodes.add(flowNode);
    //        if (historicActivityInstance.getEndTime() != null) {
    //            finishedActivityInstances.add(historicActivityInstance);
    //        }
    //    }
    //
    //    FlowNode currentFlowNode = null;
    //    FlowNode targetFlowNode = null;
    //    // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
    //    for (HistoricActivityInstance currentActivityInstance : finishedActivityInstances) {
    //        // 获得当前活动对应的节点信息及outgoingFlows信息
    //        currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
    //        List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();
    //
    //        /**
    //         * 遍历outgoingFlows并找到已已流转的 满足如下条件认为已已流转:
    //         * 1.当前节点是并行网关或兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
    //         * 2.当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
    //         */
    //        if ("parallelGateway".equals(currentActivityInstance.getActivityType())
    //                || "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
    //            // 遍历历史活动节点,找到匹配流程目标节点的
    //            for (SequenceFlow sequenceFlow : sequenceFlows) {
    //                targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
    //                if (historicActivityNodes.contains(targetFlowNode)) {
    //                    highLightedFlowIds.add(sequenceFlow.getId());
    //                }
    //            }
    //        } else {
    //            List<Map<String, Object>> tempMapList = new ArrayList<>();
    //            for (SequenceFlow sequenceFlow : sequenceFlows) {
    //                for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
    //                    if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
    //                        Map<String, Object> map = new HashMap<>();
    //                        map.put("highLightedFlowId", sequenceFlow.getId());
    //                        map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
    //                        tempMapList.add(map);
    //                    }
    //                }
    //            }
    //
    //            if (!CollectionUtils.isEmpty(tempMapList)) {
    //                // 遍历匹配的集合,取得开始时间最早的一个
    //                long earliestStamp = 0L;
    //                String highLightedFlowId = null;
    //                for (Map<String, Object> map : tempMapList) {
    //                    long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
    //                    if (earliestStamp == 0 || earliestStamp == highLightedFlowStartTime) {
    //                        highLightedFlowId = map.get("highLightedFlowId").toString();
    //                        earliestStamp = highLightedFlowStartTime;
    //                    }
    //                }
    //
    //                highLightedFlowIds.add(highLightedFlowId);
    //            }
    //
    //        }
    //
    //    }
    //    return highLightedFlowIds;
    //}

    /**
     * @param bpmnModel                    bpmnModel
     * @param historicActivityInstanceList historicActivityInstanceList
     * @return HighLightedFlows
     */
    public List<String> getHighLightedFlows(BpmnModel bpmnModel,
                                            List<HistoricActivityInstance> historicActivityInstanceList) {
        //historicActivityInstanceList 是 流程中已经执行的历史活动实例
        // 已经流经的顺序流,需要高亮显示
        List<String> highFlows = new ArrayList<>();

        // 全部活动节点
        List<FlowNode> allHistoricActivityNodeList = new ArrayList<>();

        // 拥有endTime的历史活动实例,即已经完成了的节点
        List<HistoricActivityInstance> finishedActivityInstancesList = new ArrayList<>();

        /*
         * 循环的目的:
         *           获取所有的历史节点FlowNode并放入allHistoricActivityNodeList
         *           获取所有确定结束了的历史节点finishedActivityInstancesList
         */
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            // 获取流程节点
            // bpmnModel.getMainProcess()获取一个Process对象
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            allHistoricActivityNodeList.add(flowNode);

            // 如果结束时间不为空,表示当前节点已经完成
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstancesList.add(historicActivityInstance);
            }
        }

        FlowNode currentFlowNode;
        FlowNode targetFlowNode;
        HistoricActivityInstance currentActivityInstance;

        // 遍历已经完成的活动实例,从每个实例的outgoingFlows中找到已经执行的
        for (int k = 0; k < finishedActivityInstancesList.size(); k++) {
            currentActivityInstance = finishedActivityInstancesList.get(k);

            // 获得当前活动对应的节点信息以及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);

            // 当前节点的所有流出线
            List<SequenceFlow> outgoingFlowList = currentFlowNode.getOutgoingFlows();

            /*
             * 遍历outgoingFlows并找到已经流转的  满足如下条件认为已经流转:
             * 1、当前节点是并行网关或者兼容网关,则通过outgoingFlows能够在历史活动中找到的全部节点均为已经流转
             * 2、当前节点是以上两种类型之外的,通过outgoingFlows查找到的时间最早的流转节点视为有效流转
             * (第二点有问题,有过驳回的,会只绘制驳回的流程线,通过走向下一级的流程线没有高亮显示)
             */
            if ("parallelGateway".equals(currentActivityInstance.getActivityType()) ||
                    "inclusiveGateway".equals(currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点,找到匹配流程目标节点的
                for (SequenceFlow outgoingFlow : outgoingFlowList) {
                    // 获取当前节点流程线对应的下一级节点
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(outgoingFlow.getTargetRef(), true);

                    // 如果下级节点包含在所有历史节点中,则将当前节点的流出线高亮显示
                    if (allHistoricActivityNodeList.contains(targetFlowNode)) {
                        highFlows.add(outgoingFlow.getId());
                    }
                }
            } else {
                /*
                 * 2、当前节点不是并行网关或者兼容网关
                 * 【已解决-问题】如果当前节点有驳回功能,驳回到申请节点,
                 * 则因为申请节点在历史节点中,导致当前节点驳回到申请节点的流程线被高亮显示,但实际并没有进行驳回操作
                 */
                List<Map<String, Object>> tempMapList = new ArrayList<>();

                // 当前节点ID
                String currentActivityId = currentActivityInstance.getActivityId();

                int size = historicActivityInstanceList.size();
                boolean ifStartFind = false;
                boolean ifFinded = false;
                HistoricActivityInstance historicActivityInstance;

                // 循环当前节点的所有流出线
                // 循环所有的历史节点
//                log.info("【开始】-匹配当前节点-ActivityId=【{}】需要高亮显示的流出线", currentActivityId);
//                log.info("循环历史节点");

                for (int i = 0; i < size; i++) {
                    // // 如果当前节点流程线对应的下级节点在历史节点中,则该条流程线进行高亮显示(【问题】有驳回流程线时,即使没有进行驳回操作,因为申请节点在历史节点中,也会将驳回流程线高亮显示-_-||)
                    // if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                    // Map<String, Object> map = new HashMap<>();
                    // map.put("highLightedFlowId", sequenceFlow.getId());
                    // map.put("highLightedFlowStartTime", historicActivityInstance.getStartTime().getTime());
                    // tempMapList.add(map);
                    // // highFlows.add(sequenceFlow.getId());
                    // }

                    // 历史节点
                    historicActivityInstance = historicActivityInstanceList.get(i);
//                    log.info("第【{}/{}】个历史节点-ActivityId=【{}】", i + 1, size, historicActivityInstance.getActivityId());

                    // 如果循环历史节点中的id等于当前节点id,从当前历史节点继续先后查找是否有当前流程线等于的节点
                    // 历史节点的序号需要大于等于已经完成历史节点的序号,放置驳回重审一个节点经过两次时只取第一次的流出线高亮显示,第二次的不显示
                    if (i >= k && historicActivityInstance.getActivityId().equals(currentActivityId)) {
//                        log.info("第【{}】个历史节点和当前节点一致-ActivityId=【{}】", i + 1, historicActivityInstance.getActivityId());
                        ifStartFind = true;
                        // 跳过当前节点继续查找下一个节点
                        continue;
                    }
                    if (ifStartFind) {
//                        log.info("[开始]-循环当前节点-ActivityId=【{}】的所有流出线", currentActivityId);

                        ifFinded = false;
                        for (SequenceFlow sequenceFlow : outgoingFlowList) {
                            // 如果当前节点流程线对应的下级节点在其后面的历史节点中,则该条流程线进行高亮显示
                            // 【问题】
//                            log.info("当前流出线的下级节点=[{}]", sequenceFlow.getTargetRef());
                            if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
//                                log.info("当前节点[{}]需高亮显示的流出线=[{}]", currentActivityId, sequenceFlow.getId());
                                highFlows.add(sequenceFlow.getId());
                                // 暂时默认找到离当前节点最近的下一级节点即退出循环,否则有多条流出线时将全部被高亮显示
                                ifFinded = true;
                                break;
                            }
                        }
//                        log.info("[完成]-循环当前节点-ActivityId=【{}】的所有流出线", currentActivityId);
                    }
                    if (ifFinded) {
                        // 暂时默认找到离当前节点最近的下一级节点即退出历史节点循环,否则有多条流出线时将全部被高亮显示
                        break;
                    }
                }
//                log.info("【完成】-匹配当前节点-ActivityId=【{}】需要高亮显示的流出线", currentActivityId);
                // if (!CollectionUtils.isEmpty(tempMapList)) {
                // // 遍历匹配的集合,取得开始时间最早的一个
                // long earliestStamp = 0L;
                // String highLightedFlowId = null;
                // for (Map<String, Object> map : tempMapList) {
                // long highLightedFlowStartTime = Long.valueOf(map.get("highLightedFlowStartTime").toString());
                // if (earliestStamp == 0 || earliestStamp <= highLightedFlowStartTime) {
                // highLightedFlowId = map.get("highLightedFlowId").toString();
                // earliestStamp = highLightedFlowStartTime;
                // }
                // }
                // highFlows.add(highLightedFlowId);
                // }
            }
        }
        return highFlows;
    }

    /**
     * @param bpmnModel                    bpmnModel
     * @param historicActivityInstanceList historicActivityInstanceList
     * @return HighLightedFlows
     */
    public List<String> getHighLightedFlowsByIncomingFlows(BpmnModel bpmnModel,
                                                           List<HistoricActivityInstance> historicActivityInstanceList) {
        //historicActivityInstanceList 是 流程中已经执行的历史活动实例
        // 已经流经的顺序流,需要高亮显示
        List<String> highFlows = new ArrayList<>();

        // 全部活动节点(包括正在执行的和未执行的)
        List<FlowNode> allHistoricActivityNodeList = new ArrayList<>();

        /*
         * 循环的目的:
         *           获取所有的历史节点FlowNode并放入allHistoricActivityNodeList
         *           获取所有确定结束了的历史节点finishedActivityInstancesList
         */
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            // 获取流程节点
            // bpmnModel.getMainProcess()获取一个Process对象
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            allHistoricActivityNodeList.add(flowNode);
        }
        // 循环活动节点
        for (FlowNode flowNode : allHistoricActivityNodeList) {
            // 获取每个活动节点的输入线
            List<SequenceFlow> incomingFlows = flowNode.getIncomingFlows();

            // 循环输入线,如果输入线的源头处于全部活动节点中,则将其包含在内
            for (SequenceFlow sequenceFlow : incomingFlows) {
                if (allHistoricActivityNodeList.stream().map(BaseElement::getId).collect(Collectors.toList()).contains(sequenceFlow.getSourceFlowElement().getId())){
                    highFlows.add(sequenceFlow.getId());
                }
            }
        }
        return highFlows;
    }


    private List<String> getRunningActivityFlowsIds(BpmnModel bpmnModel, List<String> runningActivityIdList, List<HistoricActivityInstance> historicActivityInstanceList) {
        List<String> runningActivityFlowsIds = new ArrayList<>();
        List<String> runningActivityIds = new ArrayList<>(runningActivityIdList);
        // 逆序寻找,因为historicActivityInstanceList有序
        if (CollectionUtils.isEmpty(runningActivityIds)) {
            return runningActivityFlowsIds;
        }
        for (int i = historicActivityInstanceList.size() - 1; i >= 0; i--) {
            HistoricActivityInstance historicActivityInstance = historicActivityInstanceList.get(i);
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            // 如果当前节点是未完成的节点
            if (runningActivityIds.contains(flowNode.getId())) {
                continue;
            }
            // 当前节点的所有流出线
            List<SequenceFlow> outgoingFlowList = flowNode.getOutgoingFlows();
            // 遍历所有的流出线
            for (SequenceFlow outgoingFlow : outgoingFlowList) {
                // 获取当前节点流程线对应的下一级节点
                FlowNode targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(outgoingFlow.getTargetRef(), true);
                // 如果找到流出线的目标是runningActivityIdList中的,那么添加后将其移除,避免找到重复的都指向runningActivityIdList的流出线
                if (runningActivityIds.contains(targetFlowNode.getId())) {
                    runningActivityFlowsIds.add(outgoingFlow.getId());
                    runningActivityIds.remove(targetFlowNode.getId());
                }
            }

        }
        return runningActivityFlowsIds;
    }

}

  1. 回退任务代码
package com.gxkj.common.modular.activiti.config;

import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.impl.history.HistoryManager;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.IdentityLinkEntityManager;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;

public class JumpAnyWhereCmd implements Command {
    private String taskId;

    private String targetNodeId;

    public JumpAnyWhereCmd(String taskId, String targetNodeId) {
        this.taskId = taskId;
        this.targetNodeId = targetNodeId;
    }

    public Object execute(CommandContext commandContext) {
        //获取任务实例管理类
        TaskEntityManager taskEntityManager = commandContext.getTaskEntityManager();
        //获取当前任务实例
        TaskEntity currentTask = taskEntityManager.findById(taskId);

        //获取当前节点的执行实例
        ExecutionEntity execution = currentTask.getExecution();
        String executionId = execution.getId();

        //获取流程定义id
        String processDefinitionId = execution.getProcessDefinitionId();
        //获取目标节点
        Process process = ProcessDefinitionUtil.getProcess(processDefinitionId);
        FlowElement flowElement = process.getFlowElement(targetNodeId);
        //获取历史管理
        HistoryManager historyManager = commandContext.getHistoryManager();

        //通知当前活动结束(更新act_hi_actinst)
        historyManager.recordActivityEnd(execution,"");
        //通知任务节点结束(更新act_hi_taskinst)
        historyManager.recordTaskEnd(taskId,"");

        //在任务删除前,删除act_ru_identitylink相关联数据
        IdentityLinkEntityManager identityLinkEntityManager = commandContext.getIdentityLinkEntityManager();
        identityLinkEntityManager.deleteIdentityLinksByTaskId(taskId);

        //删除正在执行的当前任务
        taskEntityManager.delete(taskId);

        //此时设置执行实例的当前活动节点为目标节点
        execution.setCurrentFlowElement(flowElement);

        //向operations中压入继续流程的操作类
        commandContext.getAgenda().planContinueProcessOperation(execution);

        return null;
    }
}


5.TaskVo

package com.gxkj.common.modular.htgl.model;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * <p>
 * 流程实体Vo
 * </p>
 *
 * @author jopson
 */
@Data
@ApiModel(value = "TaskVo", description = "流程实体Vo")
@JsonIgnoreProperties(value = {"handler"})
public class TaskVo implements Serializable {

    @ApiModelProperty(value = "流程名称")
    private String taskName;

    @ApiModelProperty(value = "流程id")
    private String taskId;

    @ApiModelProperty(value = "预算id")
    private String budgetId;

    @ApiModelProperty(name = "流程节点key")
    private String taskDefKey;

    @ApiModelProperty(value = "预算id")
    private List<String> budgetIds;

    @ApiModelProperty(value = "指派人")
    private String assignee;

    @ApiModelProperty(value = "数量")
    private Integer num;

    @ApiModelProperty(value = "key")
    private String approveKey;

    @ApiModelProperty(value = "name")
    private String approveName;

    @ApiModelProperty(name ="流程实例id")
    @TableField(exist = false)
    private String processId;

    private String executionId;

}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐