activiti7实战教程(一)集成用户系统

  1. 新建SpringBoot项目版本号2.6.3
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.6.3</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>activitidemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>activitidemo</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

  2. 添加依赖
     <!--工作流引擎-->
            <!-- https://mvnrepository.com/artifact/org.activiti/activiti-spring-boot-starter -->
            <dependency>
                <groupId>org.activiti</groupId>
                <artifactId>activiti-spring-boot-starter</artifactId>
                <version>7.1.0.M2</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
     <!--流程图片引擎-->
            <!-- https://mvnrepository.com/artifact/org.activiti/activiti-image-generator -->
            <dependency>
                <groupId>org.activiti</groupId>
                <artifactId>activiti-image-generator</artifactId>
                <version>7.1.0.M2</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatis</groupId>
                        <artifactId>mybatis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
  3. 添加Activiti7配置

    spring:
      activiti:
        database-schema-update: true # 对所有表更新操作, 如不存在则创建
        history-level: full # 保存历史数据的最高级别
        db-history-used: true # 使用历史表
        check-process-definitions: true # 校验流程文件:true-开启(默认)、false-关闭
  4. 去掉SpringSecurity框架,修改启动类上的SpringBootApplication注解如下:

    @SpringBootApplication(
            exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class,
                    org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration.class,
            })
    public class ActivitidemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ActivitidemoApplication.class, args);
        }
    
    }
  5. 集成四个类分别如下

    UserDetailsServiceImpl、UserGroupManagerImpl、Activiti7ApplicationConfiguration、SecurityUtil
  6. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import lombok.AllArgsConstructor;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * Activiti7配置文件-用户管理器
     *
     * @author Lenovo
     */
    @AllArgsConstructor
    public class UserDetailsServiceImpl implements UserDetailsService {
        private UserRoleService userRoleService;
    
        @Override
        public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
            //用户系统用的是三方免登,这里用userId作为唯一标识
            LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(UserRole::getUserId, userId);
            List<UserRole> userRoles = userRoleService.list(wrapper);
            List<SimpleGrantedAuthority> authorities = userRoles.stream().map(x -> new SimpleGrantedAuthority(x.getRoleId().toString())).collect(Collectors.toList());
    
            //这里要填上用户的账号、密码(可以不填)和角色集合
            return new User(userId, "", authorities);
        }
    }
    
    import org.activiti.api.runtime.shared.identity.UserGroupManager;
    import org.springframework.context.annotation.Primary;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * Activiti7配置文件-用户组管理器
     *
     * @author Lenovo
     */
    @Service
    @Primary
    public class UserGroupManagerImpl implements UserGroupManager {
        @Override
        public List<String> getUserGroups(String s) {
            return null;
        }
    
        @Override
        public List<String> getUserRoles(String s) {
            return null;
        }
    
        @Override
        public List<String> getGroups() {
            return null;
        }
    
        @Override
        public List<String> getUsers() {
            return null;
        }
    }
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    import javax.annotation.Resource;
    
    /**
     * Activiti7配置文件
     *
     * @author Lenovo
     */
    @Configuration
    public class Activiti7ApplicationConfiguration {
        @Resource
        private UserRoleService userRoleService;
    
        @Bean
        public UserDetailsService activitiUserDetailsService() {
            return new UserDetailsServiceImpl(userRoleService);
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.context.SecurityContextImpl;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.stereotype.Component;
    
    import java.util.Collection;
    
    /**
     * Activiti7配置文件
     *
     * @author Lenovo
     */
    @Component
    public class SecurityUtil {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        public void logInAs(String username) {
    
            UserDetails user = userDetailsService.loadUserByUsername(username);
            if (user == null) {
                throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
            }
    
            SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
                @Override
                public Collection<? extends GrantedAuthority> getAuthorities() {
                    return user.getAuthorities();
                }
    
                @Override
                public Object getCredentials() {
                    return user.getPassword();
                }
    
                @Override
                public Object getDetails() {
                    return user;
                }
    
                @Override
                public Object getPrincipal() {
                    return user;
                }
    
                @Override
                public boolean isAuthenticated() {
                    return true;
                }
    
                @Override
                public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
    
                }
    
                @Override
                public String getName() {
                    return user.getUsername();
                }
            }));
            org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
        }
    }
    

    这四个类中会出现很多报错,先不管它,往后继续集成下去。

  7. 集成Mybatis-plus

    <!--mybatis-plus-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.5.1</version>
            </dependency>
            <!--mybatis-plus多数据源插件-->
            <!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>3.5.0</version>
            </dependency>
            <!--mybatis-plus扩展插件-->
            <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-extension -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-extension</artifactId>
                <version>3.5.1</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!--数据库连接池组件-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.20</version>
            </dependency>
            <!--阿里巴巴序列化组件-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.4</version>
            </dependency>

    配置文件

    spring:
      activiti:
        database-schema-update: true # 对所有表更新操作, 如不存在则创建
        history-level: full # 保存历史数据的最高级别
        db-history-used: true # 使用历史表
        check-process-definitions: true # 校验流程文件:true-开启(默认)、false-关闭
      application:
        name: 工作流实例
      datasource:
        dynamic:
          primary: master_mysql
          strict: false
          datasource:
            druid:
              initialSize: 1
              maxActive: 20
              minIdle: 1
              maxWait: 60000
            master_mysql:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: jdbc:mysql://localhost:3306/activiti?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
              username: root
              password: root
              type: com.alibaba.druid.pool.DruidDataSource
  8. 设计用户表User、角色表Role、用户角色关联表UserRole

    CREATE TABLE `user` (
      `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
      `login_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '账号',
      `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户名',
      `gender` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '性别',
      `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '电话',
      `photo` varchar(255) DEFAULT NULL COMMENT '照片',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `update_time` datetime DEFAULT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    CREATE TABLE `role` (
      `id` int NOT NULL,
      `role_name` varchar(255) NOT NULL COMMENT '角色名称',
      `role_code` varchar(255) NOT NULL COMMENT '角色码',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    CREATE TABLE `user_role` (
      `id` int NOT NULL,
      `user_id` int NOT NULL COMMENT '用户表id',
      `role_id` int NOT NULL COMMENT '角色表id',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  9. 使用easy_code插件生成mybatis-plus的CRUD代码

    此时发现,之前集成的四个类报错,导入相应类后全部消失。
    配置dao扫码:在启动类上加上:

    @MapperScan(basePackages = "com.example.activitidemo.dao")
  10. 用户登录的时候,我们需要手动虚拟登录到SpringSecurity中(一般是在用户登录拦截器中实现)。我这是在自定义用户参数解析器中实现:注解@User、实体类UserInfo、参数解析器RequestUserHandlerMethodArgumentResolver。(登录方式采用的是JWT

    import java.lang.annotation.*;
    
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface User {
    }
    
    import lombok.Data;
    
    import java.util.List;
    
    @Data
    public class UserInfo {
    
        private String id;
    
        private String login_name;
    
        private String user_name;
    
        private String gender;
    
        private String phone;
    
        private String photo;
    
        private String roleIds;
    }
    
    import lombok.AllArgsConstructor;
    import org.springframework.core.MethodParameter;
    import org.springframework.stereotype.Component;
    import org.springframework.util.Assert;
    import org.springframework.web.bind.support.WebDataBinderFactory;
    import org.springframework.web.context.request.NativeWebRequest;
    import org.springframework.web.context.request.ServletWebRequest;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.method.support.ModelAndViewContainer;
    
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    
    @Component
    @AllArgsConstructor
    public class RequestUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    
        private SecurityUtil securityUtil;
    
        @Override
        public boolean supportsParameter(MethodParameter methodParameter) {
            return methodParameter.hasParameterAnnotation(User.class);
        }
    
        @Override
        public UserInfo resolveArgument(MethodParameter methodParameter,
                                        ModelAndViewContainer modelAndViewContainer,
                                        NativeWebRequest nativeWebRequest,
                                        WebDataBinderFactory webDataBinderFactory) {
    
            HttpServletRequest request = ((ServletWebRequest) nativeWebRequest).getRequest();
            String token = null;
            token = request.getParameter("Authorization");
            if (token == null) {
                token = request.getHeader("Authorization");
            }
            if (token == null) {
                Cookie[] cookies = request.getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        if ("Authorization".equals(cookie.getName())) {
                            token = cookie.getValue();
                        }
                    }
                }
            }
    
            Assert.notNull(token, "未检测到token");
    
            UserInfo userInfo = JwtTokenUtil.parseToken(token, UserInfo.class);
            //这一步Activiti7需要
            securityUtil.logInAs(userInfo.getId());
            return userInfo;
        }
    }
    

    注册UserInfo的参数解析器

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
      @Resource
      private SecurityUtil securityUtil;
    
      @Override
      protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    
        // 注册UserInfo的参数分解器
        argumentResolvers.add(new RequestUserHandlerMethodArgumentResolver(securityUtil));
      }
    }
    

    忽略上面报错,因为还没有集成Jwt。

  11. 集成JWT

    <!-- JWT -->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.8.2</version>
            </dependency>
    <!--Hutool工具类-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.6.0</version>
            </dependency>

    Jwt工具类

    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.bean.copier.CopyOptions;
    import com.alibaba.fastjson.JSON;
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTCreator;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.interfaces.Claim;
    import com.auth0.jwt.interfaces.DecodedJWT;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author Lenovo
     */
    @Slf4j
    @Component
    public class JwtTokenUtil {
        /**
         * 盐
         */
        public static String SECRET = "79e7c61239681b8270162386e6daa53d1dd";
        private static final long EXPIRATION = 28800000000L;
    
        /*生成token*/
        public static <T> String generateToken(T t) {
            Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000);
            Date now = new Date();
            Map<String, Object> map = new HashMap<>();
            map.put("alg", "HS256");
            map.put("typ", "JWT");
            JWTCreator.Builder token = JWT.create()
                    .withHeader(map)
                    .withExpiresAt(expireDate)
                    .withIssuedAt(now)
                    .withNotBefore(now);
            if (t instanceof Map) {
                ((Map) t).forEach((k, v) -> token.withClaim(k + "", v + ""));
            } else {
                BeanUtil.beanToMap(t).forEach((x, y) -> token.withClaim(x, y + ""));
            }
            return token.sign(Algorithm.HMAC256(SECRET));
        }
    
        /*解析token*/
        public static <T> T parseToken(String token, Class<T> aclass) {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            DecodedJWT jwt = verifier.verify(token);
            Map<String, Claim> claims = jwt.getClaims();
            HashMap<String, Object> hashMap = new HashMap<>();
            claims.forEach((k, v) -> hashMap.put(k, v.asString()));
            T t = BeanUtil.mapToBean(hashMap, aclass, false, CopyOptions.create());
            log.info("解析Token的内容:" + t);
            return t;
        }
    
        /*解析token*/
        public static <T> T parseToken001(String token, Class<T> aclass) {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            DecodedJWT jwt = verifier.verify(token);
            Map<String, Claim> claims = jwt.getClaims();
            String string = claims.get("loginId").asString();
            T t = JSON.parseObject(string, aclass);
            log.info("解析Token的内容:" + t);
            return t;
        }
    }
    

    支持跨域(可选)

    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.stereotype.Component;
    
    /**
     * 跨域过滤器
     * @author 
     *
     */
    @Component
    public class CorsFilter implements Filter {
    
        static final String OPTIONS = "OPTIONS";
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
    
            String origin = request.getHeader("Origin");    // 获得客户端domain
            if(origin == null) {
                origin = request.getHeader("Referer");
            }
            response.setHeader("Access-Control-Allow-Origin", origin);            // 允许指定域访问跨域资源
            response.setHeader("Access-Control-Allow-Credentials", "true");       // 允许客户端携带跨域cookie,此时origin值不能为“*”,只能为指定单一域名
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken");	// 允许的header参数
    //        response.setHeader("Access-Control-Allow-Headers", "*");  // 允许的header参数
    
            // 如果是预检请求,直接返回
            if(OPTIONS.equals(request.getMethod())) {
                System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
                response.getWriter().print("");
                return;
            }
    
            //System.out.println("*********************************过滤器被使用**************************2233");
            chain.doFilter(req, res);
        }
        @Override
        public void init(FilterConfig filterConfig) {}
    
        @Override
        public void destroy() {}
    
    
    }
    

  12. 工具类

    
    import cn.hutool.core.lang.Assert;
    import cn.hutool.core.util.ClassUtil;
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.db.sql.SqlExecutor;
    import de.odysseus.el.ExpressionFactoryImpl;
    import de.odysseus.el.util.SimpleContext;
    import lombok.AllArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.activiti.api.runtime.shared.query.Page;
    import org.activiti.bpmn.model.*;
    import org.activiti.engine.*;
    import org.activiti.engine.history.HistoricActivityInstance;
    import org.activiti.engine.history.HistoricProcessInstance;
    import org.activiti.engine.history.HistoricProcessInstanceQuery;
    import org.activiti.engine.history.HistoricTaskInstance;
    import org.activiti.engine.repository.Deployment;
    import org.activiti.engine.repository.ProcessDefinition;
    import org.activiti.engine.runtime.Execution;
    import org.activiti.engine.runtime.ExecutionQuery;
    import org.activiti.engine.runtime.ProcessInstance;
    import org.activiti.engine.task.Comment;
    import org.activiti.engine.task.Task;
    import org.activiti.engine.task.TaskQuery;
    import org.activiti.image.ProcessDiagramGenerator;
    import org.activiti.image.impl.DefaultProcessDiagramGenerator;
    import org.activiti.runtime.api.query.impl.PageImpl;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.util.ObjectUtils;
    import org.springframework.util.StringUtils;
    
    import javax.el.ExpressionFactory;
    import javax.el.ValueExpression;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.*;
    
    
    /**
     * activiti7工具类
     *
     * @author Lenovo
     */
    @Component
    @Slf4j
    @AllArgsConstructor
    public class Activiti7Util {
        private RepositoryService repositoryService;
        private RuntimeService runtimeService;
        private TaskService taskService;
        private HistoryService historyService;
    
        /**
         * 流程部署
         *
         * @param name          流程名称,例:学生请假
         * @param deploymentKey 流程Key,例:student_leave
         * @param resourcePath  资源文件路径,例:processes/student_leave.bpmn20
         * @return 部署实例
         */
        public Deployment deploy(String name, String deploymentKey, String resourcePath) {
            List<Deployment> deployments = repositoryService.createDeploymentQuery().deploymentKey(deploymentKey).list();
            Assert.isTrue(deployments.size() == 0, "重复部署");
            return repositoryService.createDeployment()
                    .name(name)
                    .key(deploymentKey)
                    .addClasspathResource(resourcePath + ".xml")
                    .addClasspathResource(resourcePath + ".png")
                    .deploy();
        }
    
        /**
         * 取消部署
         *
         * @param deploymentKey 流程Key,例:student_leave
         * @param cascade       是否级联删除所有关联的流程及其历史记录
         */
        public void undeploy(String deploymentKey, Boolean cascade) {
            List<Deployment> deployments = repositoryService.createDeploymentQuery().deploymentKey(deploymentKey).list();
            for (Deployment deployment : deployments) {
                repositoryService.deleteDeployment(deployment.getId(), cascade);
            }
        }
    
        /**
         * 获取所有流程定义
         *
         * @param startNum 分页开始下标 从0开始
         * @param endNum   分页结束下标
         * @return 流程定义list
         */
        public Page<ProcessDefinition> getProcessDefinitionList(Integer startNum, Integer endNum) {
            List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery().listPage(startNum, endNum);
            long count = repositoryService.createProcessDefinitionQuery().count();
            return new PageImpl<ProcessDefinition>(processDefinitions, (int) count);
        }
    
        /**
         * 删除25张Activiti的数据表
         */
        public int dropActivitiTables(Connection connection) throws SQLException {
            String sql = "DROP TABLE ACT_EVT_LOG,\n" +
                    "ACT_GE_BYTEARRAY,\n" +
                    "ACT_GE_PROPERTY,\n" +
                    "ACT_HI_ACTINST,\n" +
                    "ACT_HI_ATTACHMENT,\n" +
                    "ACT_HI_COMMENT,\n" +
                    "ACT_HI_DETAIL,\n" +
                    "ACT_HI_IDENTITYLINK,\n" +
                    "ACT_HI_PROCINST,\n" +
                    "ACT_HI_TASKINST,\n" +
                    "ACT_HI_VARINST,\n" +
                    "ACT_PROCDEF_INFO,\n" +
                    "ACT_RE_DEPLOYMENT,\n" +
                    "ACT_RE_MODEL,\n" +
                    "ACT_RE_PROCDEF,\n" +
                    "ACT_RU_DEADLETTER_JOB,\n" +
                    "ACT_RU_EVENT_SUBSCR,\n" +
                    "ACT_RU_EXECUTION,\n" +
                    "ACT_RU_IDENTITYLINK,\n" +
                    "ACT_RU_INTEGRATION,\n" +
                    "ACT_RU_JOB,\n" +
                    "ACT_RU_SUSPENDED_JOB,\n" +
                    "ACT_RU_TASK,\n" +
                    "ACT_RU_TIMER_JOB,\n" +
                    "ACT_RU_VARIABLE";
            int execute = SqlExecutor.execute(connection, sql);
            connection.close();
            return execute;
        }
    
        /**
         * 发起流程
         *
         * @param processDefinitionKey 流程定义Key,例:student_leave
         * @param businessKey          关联的业务表ID
         * @param variables            Assignee...等预定义参数
         * @return 流程实例
         */
        public ProcessInstance startProcessInstance(String processDefinitionKey, String businessKey, Map<String, Object> variables) {
            return runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
        }
    
        /**
         * 待批任务
         *
         * @param assignee 用户标识(一般是用户ID)
         * @return 分页数据
         */
        public Page<Map<String, Object>> getAssigneeTasks(String assignee, int firstResult, int maxResults) {
            TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assignee);
            //分页
            long count = taskQuery.count();
            List<Task> tasks = taskQuery.listPage(firstResult, maxResults);
    
            //初始化数据容器
            ArrayList<Map<String, Object>> list = new ArrayList<>();
            for (Task task : tasks) {
                HashMap<String, Object> map = this.taskDetail(task.getId());
                list.add(map);
            }
            return new PageImpl<Map<String, Object>>(list, (int) count);
        }
    
        /**
         * 我发起的流程实例
         *
         * @param assignee   我的用户标识(userId)
         * @param isFinished 是否完成
         * @param before     在X时间节点之前
         * @param after      在X时间节点之后
         * @return 分页数据
         */
        public Page<Map<String, Object>> mindProcessInstance(String assignee, int firstResult, int maxResults, Boolean isFinished, Date before, Date after) {
            HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery().startedBy(assignee);
            //查询条件
            if (isFinished != null) {
                if (isFinished) {
                    historicProcessInstanceQuery.finished();
                } else {
                    historicProcessInstanceQuery.unfinished();
                }
            }
            if (before != null) {
                historicProcessInstanceQuery.startedBefore(before);
            }
            if (after != null) {
                historicProcessInstanceQuery.startedAfter(after);
            }
            //分页
            long count = historicProcessInstanceQuery.count();
            List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.listPage(firstResult, maxResults);
    
            //初始化数据容器
            ArrayList<Map<String, Object>> list = new ArrayList<>();
            for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
                HashMap<String, Object> map = this.processInstanceDetail(historicProcessInstance.getId());
                list.add(map);
            }
            return new PageImpl<Map<String, Object>>(list, (int) count);
        }
    
        /**
         * 流程实例详情
         *
         * @param processInstanceId 流程实例ID
         */
        public HashMap<String, Object> processInstanceDetail(String processInstanceId) {
            //历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            Deployment deployment = repositoryService.createDeploymentQuery().processDefinitionKey(historicProcessInstance.getProcessDefinitionKey()).singleResult();
            //运行中流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(historicProcessInstance.getId()).singleResult();
            HashMap<String, Object> map = new HashMap<>();
            //流程部署名称
            map.put("deploymentName", deployment.getName());
            //流程实例ID
            map.put("processInstanceId", historicProcessInstance.getId());
            //流程实例ID
            map.put("processDefinitionKey", historicProcessInstance.getProcessDefinitionKey());
            //业务Key
            map.put("processInstanceBusinessKey", historicProcessInstance.getBusinessKey());
            //流程发起时间
            map.put("processInstanceStartTime", historicProcessInstance.getStartTime());
            //流程发起人
            map.put("processInstanceStartUserId", historicProcessInstance.getStartUserId());
            //流程是否结束
            if (processInstance == null) {
                map.put("isFinished", true);
            } else {
                map.put("isFinished", false);
                //查出当前审批节点
                HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).unfinished().singleResult();
                //当前节点审批人ID
                map.put("currentAssigneeUserId", historicTaskInstance.getAssignee());
                //任务名称
                map.put("currentTaskName", historicTaskInstance.getName());
            }
            return map;
        }
    
        /**
         * 任务详情
         *
         * @param taskId 任务ID
         */
        public HashMap<String, Object> taskDetail(String taskId) {
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            //流程实例ID
            String processInstanceId = task.getProcessInstanceId();
            //流程实例
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            //流程部署实例
            Deployment deployment = repositoryService.createDeploymentQuery().processDefinitionKey(processInstance.getProcessDefinitionKey()).singleResult();
            /**
             * 组装需要数据
             */
            HashMap<String, Object> map = new HashMap<>();
            //流程部署名称
            map.put("deploymentName", deployment.getName());
    
            //任务ID
            map.put("taskId", taskId);
            //任务名称
            map.put("taskName", task.getName());
            //任务描述
            map.put("taskDescription", task.getDescription());
            //任务的紧迫性【int】
            map.put("taskPriority", task.getPriority());
            //负责此任务的人员
            map.put("taskOwner", task.getOwner());
            //将此任务委派给的对象
            map.put("taskAssigneeUserId", task.getAssignee());
            //任务创建的时间
            map.put("taskCreateTime", task.getCreateTime());
            //任务截止日期
            map.put("taskDueDate", task.getDueDate());
            //任务类别
            map.put("taskCategory", task.getCategory());
            //任务的流程变量
            map.put("taskProcessVariables", task.getProcessVariables());
            //任务领取时间
            map.put("taskClaimTime", task.getClaimTime());
    
            //流程实例名称
            map.put("processInstanceName", processInstance.getName());
            //流程定义Key
            map.put("processDefinitionKey", processInstance.getProcessDefinitionKey());
            //流程实例关联的业务表ID
            map.put("processInstanceBusinessKey", processInstance.getBusinessKey());
            //流程实例是否被挂起
            map.put("processInstanceIsSuspended", processInstance.isSuspended());
            //流程实例变量
            map.put("processInstanceProcessVariables", processInstance.getProcessVariables());
            //流程实例描述
            map.put("processInstanceDescription", processInstance.getDescription());
            //流程实例开始的时间
            map.put("processInstanceStartTime", processInstance.getStartTime());
            //流程实例发起人的ID
            map.put("processInstanceStartUserId", processInstance.getStartUserId());
            return map;
        }
    
        /**
         * 获取当前任务节点的下一个任务节点(UserTask或者EndEvent),拿到值后判断类型后进行强转。
         *
         * @param taskId 当前任务节点ID
         * @return 下个任务节点2
         */
        public FlowElement getNextUserFlowElement(String taskId) {
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            // 取得已提交的任务
            HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
                    .taskId(task.getId()).singleResult();
    
            // 获得流程定义
            ProcessDefinition processDefinition = repositoryService.getProcessDefinition(historicTaskInstance.getProcessDefinitionId());
    
            //获得当前流程的活动ID
            ExecutionQuery executionQuery = runtimeService.createExecutionQuery();
            Execution execution = executionQuery.executionId(historicTaskInstance.getExecutionId()).singleResult();
            String activityId = execution.getActivityId();
            UserTask userTask = null;
            while (true) {
                //根据活动节点获取当前的组件信息
                FlowNode flowNode = getFlowNode(processDefinition.getId(), activityId);
                //获取该节点之后的流向
                List<SequenceFlow> sequenceFlowListOutGoing = flowNode.getOutgoingFlows();
    
                // 获取的下个节点不一定是userTask的任务节点,所以要判断是否是任务节点
                if (sequenceFlowListOutGoing.size() > 1) {
                    // 如果有1条以上的出线,表示有分支,需要判断分支的条件才能知道走哪个分支
                    // 遍历节点的出线得到下个activityId
                    activityId = getNextActivityId(execution.getId(), task.getProcessInstanceId(), sequenceFlowListOutGoing);
                } else if (sequenceFlowListOutGoing.size() == 1) {
                    // 只有1条出线,直接取得下个节点
                    SequenceFlow sequenceFlow = sequenceFlowListOutGoing.get(0);
                    // 下个节点
                    FlowElement flowElement = sequenceFlow.getTargetFlowElement();
                    if (flowElement instanceof UserTask) {
                        // 下个节点为UserTask时
                        userTask = (UserTask) flowElement;
                        System.out.println("下个任务为:" + userTask.getName());
                        return userTask;
                    } else if (flowElement instanceof ExclusiveGateway) {
                        // 下个节点为排它网关时
                        ExclusiveGateway exclusiveGateway = (ExclusiveGateway) flowElement;
                        List<SequenceFlow> outgoingFlows = exclusiveGateway.getOutgoingFlows();
                        // 遍历网关的出线得到下个activityId
                        activityId = getNextActivityId(execution.getId(), task.getProcessInstanceId(), outgoingFlows);
                        FlowNode flowNode_ = getFlowNode(processDefinition.getId(), activityId);
                        if (flowNode_ instanceof UserTask) {
                            return flowNode_;
                        }
                    } else if (flowElement instanceof EndEvent) {
                        //下个节点是结束节点
                        return flowElement;
                    }
                } else {
                    // 没有出线,则表明是结束节点
                    return null;
                }
            }
        }
    
        /**
         * 任务处理(同意)
         *
         * @param taskId    任务ID
         * @param comment   处理批注
         * @param variables 预定义参数值
         */
        public void disposeTask(String taskId, String comment, Map<String, Object> variables) {
            //如果没有指定下一步审批人,则不让处理
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
            FlowElement flowElement = this.getNextUserFlowElement(task.getId());
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                String assignee = userTask.getAssignee();
                Object o = variables.get(this.getVariableNameByExpression(assignee));
                Assert.isTrue(o != null && StrUtil.isNotBlank(o.toString()), "未指定下一步审批人");
            }
            taskService.addComment(taskId, task.getProcessInstanceId(), comment);
            taskService.complete(taskId, variables);
    
        }
    
        /**
         * 终止任务,指向结束节点
         *
         * @param taskId  任务ID
         * @param comment 任务批注
         */
        public void endProcess(String taskId, String comment) {
            //  当前任务
            Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
    
            BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
            List<EndEvent> endEventList = bpmnModel.getMainProcess().findFlowElementsOfType(EndEvent.class);
            FlowNode endFlowNode = endEventList.get(0);
            FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());
    
            //  临时保存当前活动的原始方向
            List<SequenceFlow> originalSequenceFlowList = new ArrayList<>(currentFlowNode.getOutgoingFlows());
            //  清理活动方向
            currentFlowNode.getOutgoingFlows().clear();
    
            //  建立新方向
            SequenceFlow newSequenceFlow = new SequenceFlow();
            newSequenceFlow.setId("newSequenceFlowId");
            newSequenceFlow.setSourceFlowElement(currentFlowNode);
            newSequenceFlow.setTargetFlowElement(endFlowNode);
            List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
            newSequenceFlowList.add(newSequenceFlow);
            //  当前节点指向新的方向
            currentFlowNode.setOutgoingFlows(newSequenceFlowList);
            //任务批注
            taskService.addComment(taskId, task.getProcessInstanceId(), comment);
            //  完成当前任务
            taskService.complete(task.getId());
            //  可以不用恢复原始方向,不影响其它的流程
            currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
        }
    
    
        /**
         * 退回到上一节点
         *
         * @param task 当前任务
         */
        public void backProcess(Task task, String comment) throws Exception {
    
            String processInstanceId = task.getProcessInstanceId();
            // 取得所有历史任务按时间降序排序
            List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .orderByTaskCreateTime()
                    .desc()
                    .list();
            int size = 2;
            if (ObjectUtils.isEmpty(htiList) || htiList.size() < size) {
                return;
            }
            // list里的第二条代表上一个任务
            HistoricTaskInstance lastTask = htiList.get(1);
    
            // list里第一条代表当前任务
            HistoricTaskInstance curTask = htiList.get(0);
    
            // 当前节点的executionId
            String curExecutionId = curTask.getExecutionId();
    
    
            // 上个节点的taskId
            String lastTaskId = lastTask.getId();
            // 上个节点的executionId
            String lastExecutionId = lastTask.getExecutionId();
    
            if (null == lastTaskId) {
                throw new Exception("LAST TASK IS NULL");
            }
    
            String processDefinitionId = lastTask.getProcessDefinitionId();
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
    
            String lastActivityId = null;
            List<HistoricActivityInstance> haiFinishedList = historyService.createHistoricActivityInstanceQuery()
                    .executionId(lastExecutionId).finished().list();
    
            for (HistoricActivityInstance hai : haiFinishedList) {
                if (lastTaskId.equals(hai.getTaskId())) {
                    // 得到ActivityId,只有HistoricActivityInstance对象里才有此方法
                    lastActivityId = hai.getActivityId();
                    break;
                }
            }
    
            // 得到上个节点的信息
            FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivityId);
    
            // 取得当前节点的信息
            Execution execution = runtimeService.createExecutionQuery().executionId(curExecutionId).singleResult();
            String curActivityId = execution.getActivityId();
            FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(curActivityId);
    
            //记录当前节点的原活动方向
            List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
            oriSequenceFlows.addAll(curFlowNode.getOutgoingFlows());
    
            //清理活动方向
            curFlowNode.getOutgoingFlows().clear();
    
            //建立新方向
            List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
            SequenceFlow newSequenceFlow = new SequenceFlow();
            newSequenceFlow.setId("newSequenceFlowId");
            newSequenceFlow.setSourceFlowElement(curFlowNode);
            newSequenceFlow.setTargetFlowElement(lastFlowNode);
            newSequenceFlowList.add(newSequenceFlow);
            curFlowNode.setOutgoingFlows(newSequenceFlowList);
    
            // 完成任务
            taskService.addComment(task.getId(), task.getProcessInstanceId(), comment);
            taskService.complete(task.getId());
    
            //恢复原方向
            curFlowNode.setOutgoingFlows(oriSequenceFlows);
    
            Task nextTask = taskService
                    .createTaskQuery().processInstanceId(processInstanceId).singleResult();
    
            // 设置执行人
            if (nextTask != null) {
                taskService.setAssignee(nextTask.getId(), lastTask.getAssignee());
            }
        }
    
        /**
         * 跳到最开始的任务节点(直接打回)
         *
         * @param task 当前任务
         */
        public void jumpToStart(Task task, String comment) {
            ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
            HistoryService historyService = processEngine.getHistoryService();
            RepositoryService repositoryService = processEngine.getRepositoryService();
            TaskService taskService = processEngine.getTaskService();
    
            String processInstanceId = task.getProcessInstanceId();
    
            //  获取所有历史任务(按创建时间升序)
            List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .orderByTaskCreateTime()
                    .asc()
                    .list();
    
            if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
                return;
            }
    
            //  第一个任务
            HistoricTaskInstance startTask = hisTaskList.get(0);
            //  当前任务
            HistoricTaskInstance currentTask = hisTaskList.get(hisTaskList.size() - 1);
    
            BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
    
            //  获取第一个活动节点
            FlowNode startFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(startTask.getTaskDefinitionKey());
            //  获取当前活动节点
            FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentTask.getTaskDefinitionKey());
    
            //  临时保存当前活动的原始方向
            List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
            originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
            //  清理活动方向
            currentFlowNode.getOutgoingFlows().clear();
    
            //  建立新方向
            SequenceFlow newSequenceFlow = new SequenceFlow();
            newSequenceFlow.setId("newSequenceFlowId");
            newSequenceFlow.setSourceFlowElement(currentFlowNode);
            newSequenceFlow.setTargetFlowElement(startFlowNode);
            List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
            newSequenceFlowList.add(newSequenceFlow);
            //  当前节点指向新的方向
            currentFlowNode.setOutgoingFlows(newSequenceFlowList);
    
            //  完成当前任务
            taskService.addComment(task.getId(), task.getProcessInstanceId(), comment);
            taskService.complete(task.getId());
    
            //  重新查询当前任务
            Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
            if (null != nextTask) {
                taskService.setAssignee(nextTask.getId(), startTask.getAssignee());
            }
            //  恢复原始方向
            currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
        }
    
        /**
         * 根据流程实例Id,获取实时流程图片
         *
         * @param processInstanceId 流程实例ID
         * @param outputStream      输出流
         * @param useCustomColor    true:用自定义的颜色(完成节点绿色,当前节点红色),default:用默认的颜色(红色)
         */
        public void getFlowImgByInstanceId(String processInstanceId, OutputStream outputStream, boolean useCustomColor) {
            try {
                if (StringUtils.isEmpty(processInstanceId)) {
                    log.error("processInstanceId is null");
                    return;
                }
                // 获取历史流程实例
                HistoricProcessInstance historicProcessInstance = historyService
                        .createHistoricProcessInstanceQuery()
                        .processInstanceId(processInstanceId).singleResult();
                // 获取流程中已经执行的节点,按照执行先后顺序排序
                List<HistoricActivityInstance> historicActivityInstances = historyService
                        .createHistoricActivityInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .orderByHistoricActivityInstanceId()
                        .asc().list();
                // 高亮已经执行流程节点ID集合
                List<String> highLightedActivitiIds = new ArrayList<>();
                int index = 1;
                for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                    if (useCustomColor) {
                        //如果历史节点中有结束节点,则高亮结束节点
                        if ("endEvent".equalsIgnoreCase(historicActivityInstance.getActivityType())) {
                            highLightedActivitiIds.add(historicActivityInstance.getActivityId());
                        }
                        //如果没有结束时间,则是正在执行节点
                        Date endTime = historicActivityInstance.getEndTime();
                        if (endTime == null) {
                            highLightedActivitiIds.add(historicActivityInstance.getActivityId() + "#");
                        } else {
                            // 已完成节点
                            highLightedActivitiIds.add(historicActivityInstance.getActivityId());
                        }
                    } else {
                        // 用默认颜色
                        highLightedActivitiIds.add(historicActivityInstance.getActivityId());
                    }
    
                    index++;
                }
    
                ProcessDiagramGenerator processDiagramGenerator = null;
                if (useCustomColor) {
                    // 使用自定义的程序图片生成器
                    processDiagramGenerator = new CustomProcessDiagramGenerator();
    
                } else {
                    // 使用默认的程序图片生成器
                    processDiagramGenerator = new DefaultProcessDiagramGenerator();
                }
    
    
                BpmnModel bpmnModel = repositoryService
                        .getBpmnModel(historicProcessInstance.getProcessDefinitionId());
                // 高亮流程已发生流转的线id集合
                List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);
    
                // 使用默认配置获得流程图表生成器,并生成追踪图片字符流
                InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel,
                        highLightedActivitiIds, highLightedFlowIds, "宋体",
                        "微软雅黑", "黑体");
    
                // 输出图片内容
                Integer byteSize = 1024;
                byte[] b = new byte[byteSize];
                int len;
                while ((len = imageStream.read(b, 0, byteSize)) != -1) {
                    outputStream.write(b, 0, len);
                }
            } catch (Exception e) {
                log.error("processInstanceId" + processInstanceId + "生成流程图失败,原因:" + e.getMessage(), e);
            }
    
        }
    
        /**
         * 获取已经流转的线
         *
         * @param bpmnModel
         * @param historicActivityInstances
         * @return
         */
        private 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(targetFlowNode.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<>(16);
                                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 processDefinitionId
         * @param flowElementId
         * @return
         */
        public FlowNode getFlowNode(String processDefinitionId, String flowElementId) {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
            FlowElement flowElement = bpmnModel.getMainProcess().getFlowElement(flowElementId);
            return (FlowNode) flowElement;
        }
    
    
        /**
         * 根据el表达式取得满足条件的下一个activityId
         *
         * @param executionId       执行实例ID
         * @param processInstanceId 流程实例ID
         * @param outgoingFlows     出线集合
         * @return
         */
        public String getNextActivityId(String executionId,
                                        String processInstanceId,
                                        List<SequenceFlow> outgoingFlows) {
            String activityId = null;
            // 遍历出线
            for (SequenceFlow outgoingFlow : outgoingFlows) {
                // 取得线上的条件
                String conditionExpression = outgoingFlow.getConditionExpression();
                // 取得所有变量
                Map<String, Object> variables = runtimeService.getVariables(executionId);
                HashMap<String, Object> variableNames = new HashMap<>();
                // 判断网关条件里是否包含变量名
                for (String s : variables.keySet()) {
                    if (conditionExpression.contains(s)) {
                        // 找到网关条件里的变量名
                        variableNames.put(s, getVariableValue(s, processInstanceId));
                    }
                }
                // 判断el表达式是否成立
                if (isCondition(conditionExpression, variableNames)) {
                    // 取得目标节点
                    FlowElement targetFlowElement = outgoingFlow.getTargetFlowElement();
                    activityId = targetFlowElement.getId();
                    continue;
                }
            }
            return activityId;
        }
    
        /**
         * 取得流程变量的值
         *
         * @param variableName      变量名
         * @param processInstanceId 流程实例Id
         * @return
         */
        public Object getVariableValue(String variableName, String processInstanceId) {
            Execution execution = runtimeService
                    .createExecutionQuery().processInstanceId(processInstanceId).list().get(0);
            Object object = runtimeService.getVariable(execution.getId(), variableName);
            return object;
        }
    
        /**
         * 根据key和value判断el表达式是否通过
         *
         * @param el            el表达式
         * @param variableNames el表达式中的变量名和变量值
         * @return bool
         */
        public boolean isCondition(String el, Map<String, Object> variableNames) {
            ExpressionFactory factory = new ExpressionFactoryImpl();
            SimpleContext context = new SimpleContext();
            variableNames.forEach((k, v) -> {
                context.setVariable(k, factory.createValueExpression(v, ClassUtil.getClass(v)));
            });
            ValueExpression e = factory.createValueExpression(context, el, boolean.class);
            return (Boolean) e.getValue(context);
        }
    
        /**
         * 通过EL表达式获取其中的变量名
         *
         * @param expression 表达式
         * @return 变量名
         */
        public String getVariableNameByExpression(String expression) {
            return expression.replace("${", "")
                    .replace("}", "");
        }
    
        public List<Comment> getProcessComments(String processInstanceId) {
            List<Comment> historyCommnets = new ArrayList<>();
    
            List<HistoricActivityInstance> hais = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("userTask").list();
            for (HistoricActivityInstance hai : hais) {
                String historytaskId = hai.getTaskId();
                List<Comment> comments = taskService.getTaskComments(historytaskId);
                if (comments != null && comments.size() > 0) {
                    historyCommnets.addAll(comments);
                }
            }
            return historyCommnets;
        }
    }
    
    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.*;
    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 java.awt.*;
    import java.awt.font.FontRenderContext;
    import java.awt.font.LineBreakMeasurer;
    import java.awt.font.TextAttribute;
    import java.awt.font.TextLayout;
    import java.awt.geom.*;
    import java.io.*;
    import java.text.AttributedCharacterIterator;
    import java.text.AttributedString;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Represents a canvas on which BPMN 2.0 constructs can be drawn.
     * <p>
     * @see org.activiti.image.impl.DefaultProcessDiagramGenerator
     */
    public class CustomProcessDiagramCanvas {
    
        protected static final Logger LOGGER = LoggerFactory.getLogger(CustomProcessDiagramCanvas.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 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.RED;
        protected static Color HIGHLIGHT_GREEN_COLOR = Color.GREEN;
        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 String activityFontName = "Arial";
        protected String labelFontName = "Arial";
        protected String annotationFontName = "Arial";
    
        /**
         * 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 CustomProcessDiagramCanvas(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();
        }
    
        /**
         * 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 CustomProcessDiagramCanvas(int width,
                                          int height,
                                          int minX,
                                          int minY) {
            this.canvasWidth = width;
            this.canvasHeight = height;
            this.minX = minX;
            this.minY = minY;
    
            initialize();
        }
    
        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 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) {
    
                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 drawGreenHighLight(int x,
                                       int y,
                                       int width,
                                       int height) {
            Paint originalPaint = g.getPaint();
            Stroke originalStroke = g.getStroke();
    
            g.setPaint(HIGHLIGHT_GREEN_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 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(LABEL_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;
        }
    }
    
    
    import org.activiti.bpmn.model.*;
    import org.activiti.bpmn.model.Process;
    import org.activiti.image.ProcessDiagramGenerator;
    import org.activiti.image.exception.ActivitiImageException;
    import org.activiti.image.exception.ActivitiInterchangeInfoNotFoundException;
    
    import java.io.InputStream;
    import java.util.*;
    
    /**
     * Class to generate an svg based the diagram interchange information in a
     * BPMN 2.0 process.
     */
    public class CustomProcessDiagramGenerator implements ProcessDiagramGenerator {
    
        private static final String DEFAULT_ACTIVITY_FONT_NAME = "Arial";
    
        private static final String DEFAULT_LABEL_FONT_NAME = "Arial";
    
        private static final String DEFAULT_ANNOTATION_FONT_NAME = "Arial";
    
        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 CustomProcessDiagramGenerator() {
            // start event
            activityDrawInstructions.put(StartEvent.class,
                    new ActivityDrawInstruction() {
    
                        @Override
                        public void draw(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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 CustomProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel,
                                                                    List<String> highLightedActivities,
                                                                    List<String> highLightedFlows,
                                                                    String activityFontName,
                                                                    String labelFontName,
                                                                    String annotationFontName) {
    
            prepareBpmnModel(bpmnModel);
    
            CustomProcessDiagramCanvas 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(CustomProcessDiagramCanvas 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())) {
                    drawGreenHighLight(processDiagramCanvas,
                            bpmnModel.getGraphicInfo(flowNode.getId()));
                } else 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(CustomProcessDiagramCanvas processDiagramCanvas,
                                                                    BpmnModel bpmnModel,
                                                                    BaseElement sourceElement,
                                                                    BaseElement targetElement,
                                                                    List<GraphicInfo> graphicInfoList) {
            GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId());
            GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement.getId());
    
            CustomProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement);
            CustomProcessDiagramCanvas.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 CustomProcessDiagramCanvas.SHAPE_TYPE
         */
        protected static CustomProcessDiagramCanvas.SHAPE_TYPE getShapeType(BaseElement baseElement) {
            if (baseElement instanceof Task || baseElement instanceof Activity || baseElement instanceof TextAnnotation) {
                return CustomProcessDiagramCanvas.SHAPE_TYPE.Rectangle;
            } else if (baseElement instanceof Gateway) {
                return CustomProcessDiagramCanvas.SHAPE_TYPE.Rhombus;
            } else if (baseElement instanceof Event) {
                return CustomProcessDiagramCanvas.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(CustomProcessDiagramCanvas processDiagramCanvas,
                                    BpmnModel bpmnModel,
                                    Artifact artifact) {
    
            ArtifactDrawInstruction drawInstruction = artifactDrawInstructions.get(artifact.getClass());
            if (drawInstruction != null) {
                drawInstruction.draw(processDiagramCanvas,
                        bpmnModel,
                        artifact);
            }
        }
    
        private static void drawHighLight(CustomProcessDiagramCanvas processDiagramCanvas,
                                          GraphicInfo graphicInfo) {
            processDiagramCanvas.drawHighLight((int) graphicInfo.getX(),
                    (int) graphicInfo.getY(),
                    (int) graphicInfo.getWidth(),
                    (int) graphicInfo.getHeight());
        }
    
        private static void drawGreenHighLight(CustomProcessDiagramCanvas processDiagramCanvas,
                                               GraphicInfo graphicInfo) {
            processDiagramCanvas.drawGreenHighLight((int) graphicInfo.getX(),
                    (int) graphicInfo.getY(),
                    (int) graphicInfo.getWidth(),
                    (int) graphicInfo.getHeight());
        }
    
        protected static CustomProcessDiagramCanvas 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 CustomProcessDiagramCanvas((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(CustomProcessDiagramCanvas processDiagramCanvas,
                      BpmnModel bpmnModel,
                      FlowNode flowNode);
        }
    
        protected interface ArtifactDrawInstruction {
    
            void draw(CustomProcessDiagramCanvas processDiagramCanvas,
                      BpmnModel bpmnModel,
                      Artifact artifact);
        }
    }
    

  • 10
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Activiti7 是一个开源的工作流和业务流程管理平台,其教程视频可以帮助用户快速了解和上手使用该平台。这些教程视频通常包括以下内容: 1. 简介和功能介绍:教程视频会首先介绍 Activiti7 平台的基本概念、主要功能和应用领域。这部分内容帮助用户了解 Activiti7 平台的基本特点和用途。 2. 安装和配置:这一部分会详细介绍如何下载、安装和配置 Activiti7 平台。教程视频中会演示如何设置数据库连接、管理用户和角色、配置权限等操作。 3. 模型和设计:教程视频会介绍如何创建工作流模型和业务流程设计。用户可以学习如何使用 Activiti7 的建模工具来创建和编辑模型,定义流程环节、分支和约束条件等。 4. 运行和监控:在这一部分,教程视频会演示如何部署和运行 Activiti7 的流程实例,以及如何监控和调整流程的运行状态。用户可以学习如何查看任务列表、审批流程、查找流程变量等操作。 5. 高级功能:教程视频还会介绍 Activiti7 平台的一些高级功能和扩展方法,如定时任务、事件监听、任务调度等。用户可以根据自己的需求选择学习和使用这些功能。 通过观看 Activiti7 的教程视频,用户可以快速入门并掌握这个强大的工作流和业务流程管理平台的使用方法。同时,用户还可借助视频中的实例演示和操作指导来加深理解和掌握各项功能。教程视频的存在为用户提供了一个学习和使用 Activiti7 的有效途径,使用户能够轻松上手并在实际应用中发挥其价值。 ### 回答2: activiti7 教程视频是一系列关于 activiti7 工作流引擎的教学视频。活动7是一个开源的业务流程管理(BPM)和工作流引擎,它提供了强大的功能来设计、部署和执行工作流程。 这些教程视频旨在帮助用户了解和学习 activiti7 的基本概念和功能。视频内容一般涵盖 activiti7 的安装和配置、工作流程设计和建模、任务分配和执行、流程实例跟踪等方面。通过观看这些视频,用户可以逐步学习 activiti7 的使用方法,并能够根据自己的需求来设计和管理工作流程。 对于初学者来说,activiti7 教程视频是学习 activiti7 的良好起点。视频演示了实际操作和示例,使学习者更容易理解和掌握 activiti7 的核心概念和功能。通过实际操作,学习者可以迅速掌握 activiti7 的使用技巧,并能够在实际项目中应用所学知识。 此外,由于 activiti7 是一个广泛应用的工作流引擎,因此它的教程视频具有广泛的适用性。不仅适用于企业内部的流程管理,还可用于教育和培训机构的课程教学,以及开发人员对工作流引擎的学习和研究。 总之,activiti7 教程视频是学习和了解 activiti7 工作流引擎的重要资源。通过观看这些视频,用户可以系统地学习 activiti7 的各个方面,并能够更好地应用于实际项目中。 ### 回答3: Activiti 7是一个基于开源流程引擎的工作流管理系统,它提供了一个可视化的流程设计器,可以帮助开发人员快速创建、部署和管理工作流程。 Activiti 7教程视频是为了帮助开发人员更好地理解和学习Activiti 7而制作的一系列教学视频。这些视频通常涵盖了从入门到高级的各种主题,包括安装和配置Activiti 7环境、流程定义和设计、任务分配和执行、流程消息和事件等。 通过观看这些教程视频,开发人员可以更加直观地了解Activiti 7的功能和特性,并学习如何使用它来构建复杂的工作流程。教程视频通常采用实例演示的方式,结合具体案例和场景,让开发人员更好地理解如何使用Activiti 7解决实际问题。 另外,教程视频还可以帮助开发人员快速入门Activiti 7,节省学习成本和时间。通过观看视频,开发人员可以在短时间内获取必要的知识和技能,并能够尽快开始开发和使用Activiti 7。 总之,Activiti 7教程视频是一种很好的学习Activiti 7的方式,通过观看这些教程视频,开发人员可以更好地理解和掌握Activiti 7的使用方法,提高工作效率,并能够更好地应用Activiti 7来解决实际问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文子阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值