前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >activiti7实战教程(一)集成用户系统

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

作者头像
全栈程序员站长
发布2022-09-13 21:28:58
1.4K0
发布2022-09-13 21:28:58
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

新建SpringBoot项目版本号2.6.3

代码语言:javascript
复制
<?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>

添加依赖

代码语言:javascript
复制
 <!--工作流引擎-->
        <!-- 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>

添加Activiti7配置

代码语言:javascript
复制
spring:
  activiti:
    database-schema-update: true # 对所有表更新操作, 如不存在则创建
    history-level: full # 保存历史数据的最高级别
    db-history-used: true # 使用历史表
    check-process-definitions: true # 校验流程文件:true-开启(默认)、false-关闭

去掉SpringSecurity框架,修改启动类上的SpringBootApplication注解如下:

代码语言:javascript
复制
@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);
    }

}

集成四个类分别如下

代码语言:javascript
复制
UserDetailsServiceImpl、UserGroupManagerImpl、Activiti7ApplicationConfiguration、SecurityUtil
代码语言:javascript
复制
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);
    }
}
代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
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();
    }
}
代码语言:javascript
复制
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);
    }
}
  1. 这四个类中会出现很多报错,先不管它,往后继续集成下去。
  2. 集成Mybatis-plus
代码语言:javascript
复制
<!--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>

配置文件

代码语言:javascript
复制
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

设计用户表User、角色表Role、用户角色关联表UserRole

代码语言:javascript
复制
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;
代码语言:javascript
复制
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;
代码语言:javascript
复制
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;

使用easy_code插件生成mybatis-plus的CRUD代码

代码语言:javascript
复制
代码语言:javascript
复制
代码语言:javascript
复制

注册UserInfo的参数解析器

代码语言:javascript
复制
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));
  }
}
  1. 忽略上面报错,因为还没有集成Jwt。
  2. 集成JWT
代码语言:javascript
复制
<!-- 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工具类

代码语言:javascript
复制
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;
    }
}

支持跨域(可选)

代码语言:javascript
复制
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() {}


}

工具类

代码语言:javascript
复制

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);
}
}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/162633.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档