大家好,又见面了,我是你们的朋友全栈君。
- 新建SpringBoot项目版本号2.6.3
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>activitidemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>activitidemo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- 添加依赖
<!--工作流引擎--> <!-- 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配置
spring: activiti: database-schema-update: true # 对所有表更新操作, 如不存在则创建 history-level: full # 保存历史数据的最高级别 db-history-used: true # 使用历史表 check-process-definitions: true # 校验流程文件:true-开启(默认)、false-关闭
-
去掉SpringSecurity框架,修改启动类上的SpringBootApplication注解如下:
@SpringBootApplication( exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration.class, }) public class ActivitidemoApplication { public static void main(String[] args) { SpringApplication.run(ActivitidemoApplication.class, args); } }
-
集成四个类分别如下
UserDetailsServiceImpl、UserGroupManagerImpl、Activiti7ApplicationConfiguration、SecurityUtil
-
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.AllArgsConstructor; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.List; import java.util.stream.Collectors; /** * Activiti7配置文件-用户管理器 * * @author Lenovo */ @AllArgsConstructor public class UserDetailsServiceImpl implements UserDetailsService { private UserRoleService userRoleService; @Override public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { //用户系统用的是三方免登,这里用userId作为唯一标识 LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(UserRole::getUserId, userId); List<UserRole> userRoles = userRoleService.list(wrapper); List<SimpleGrantedAuthority> authorities = userRoles.stream().map(x -> new SimpleGrantedAuthority(x.getRoleId().toString())).collect(Collectors.toList()); //这里要填上用户的账号、密码(可以不填)和角色集合 return new User(userId, "", authorities); } }
import org.activiti.api.runtime.shared.identity.UserGroupManager; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import java.util.List; /** * Activiti7配置文件-用户组管理器 * * @author Lenovo */ @Service @Primary public class UserGroupManagerImpl implements UserGroupManager { @Override public List<String> getUserGroups(String s) { return null; } @Override public List<String> getUserRoles(String s) { return null; } @Override public List<String> getGroups() { return null; } @Override public List<String> getUsers() { return null; } }
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Resource; /** * Activiti7配置文件 * * @author Lenovo */ @Configuration public class Activiti7ApplicationConfiguration { @Resource private UserRoleService userRoleService; @Bean public UserDetailsService activitiUserDetailsService() { return new UserDetailsServiceImpl(userRoleService); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import java.util.Collection; /** * Activiti7配置文件 * * @author Lenovo */ @Component public class SecurityUtil { @Autowired private UserDetailsService userDetailsService; public void logInAs(String username) { UserDetails user = userDetailsService.loadUserByUsername(username); if (user == null) { throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user"); } SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() { @Override public Collection<? extends GrantedAuthority> getAuthorities() { return user.getAuthorities(); } @Override public Object getCredentials() { return user.getPassword(); } @Override public Object getDetails() { return user; } @Override public Object getPrincipal() { return user; } @Override public boolean isAuthenticated() { return true; } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { } @Override public String getName() { return user.getUsername(); } })); org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username); } }
这四个类中会出现很多报错,先不管它,往后继续集成下去。
-
集成Mybatis-plus
<!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <!--mybatis-plus多数据源插件--> <!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency> <!--mybatis-plus扩展插件--> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-extension --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--数据库连接池组件--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> </dependency> <!--阿里巴巴序列化组件--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.4</version> </dependency>
配置文件
spring: activiti: database-schema-update: true # 对所有表更新操作, 如不存在则创建 history-level: full # 保存历史数据的最高级别 db-history-used: true # 使用历史表 check-process-definitions: true # 校验流程文件:true-开启(默认)、false-关闭 application: name: 工作流实例 datasource: dynamic: primary: master_mysql strict: false datasource: druid: initialSize: 1 maxActive: 20 minIdle: 1 maxWait: 60000 master_mysql: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/activiti?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root password: root type: com.alibaba.druid.pool.DruidDataSource
-
设计用户表User、角色表Role、用户角色关联表UserRole
CREATE TABLE `user` ( `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键', `login_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '账号', `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户名', `gender` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '性别', `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '电话', `photo` varchar(255) DEFAULT NULL COMMENT '照片', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `role` ( `id` int NOT NULL, `role_name` varchar(255) NOT NULL COMMENT '角色名称', `role_code` varchar(255) NOT NULL COMMENT '角色码', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user_role` ( `id` int NOT NULL, `user_id` int NOT NULL COMMENT '用户表id', `role_id` int NOT NULL COMMENT '角色表id', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
使用easy_code插件生成mybatis-plus的CRUD代码
此时发现,之前集成的四个类报错,导入相应类后全部消失。
配置dao扫码:在启动类上加上:@MapperScan(basePackages = "com.example.activitidemo.dao")
-
用户登录的时候,我们需要手动虚拟登录到SpringSecurity中(一般是在用户登录拦截器中实现)。我这是在自定义用户参数解析器中实现:注解@User、实体类UserInfo、参数解析器RequestUserHandlerMethodArgumentResolver。(登录方式采用的是JWT)
import java.lang.annotation.*; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface User { }
import lombok.Data; import java.util.List; @Data public class UserInfo { private String id; private String login_name; private String user_name; private String gender; private String phone; private String photo; private String roleIds; }
import lombok.AllArgsConstructor; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @Component @AllArgsConstructor public class RequestUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { private SecurityUtil securityUtil; @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.hasParameterAnnotation(User.class); } @Override public UserInfo resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { HttpServletRequest request = ((ServletWebRequest) nativeWebRequest).getRequest(); String token = null; token = request.getParameter("Authorization"); if (token == null) { token = request.getHeader("Authorization"); } if (token == null) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if ("Authorization".equals(cookie.getName())) { token = cookie.getValue(); } } } } Assert.notNull(token, "未检测到token"); UserInfo userInfo = JwtTokenUtil.parseToken(token, UserInfo.class); //这一步Activiti7需要 securityUtil.logInAs(userInfo.getId()); return userInfo; } }
注册UserInfo的参数解析器
import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import javax.annotation.Resource; import java.util.List; @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Resource private SecurityUtil securityUtil; @Override protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { // 注册UserInfo的参数分解器 argumentResolvers.add(new RequestUserHandlerMethodArgumentResolver(securityUtil)); } }
忽略上面报错,因为还没有集成Jwt。
-
集成JWT
<!-- JWT --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.2</version> </dependency> <!--Hutool工具类--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.6.0</version> </dependency>
Jwt工具类
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import com.alibaba.fastjson.JSON; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @author Lenovo */ @Slf4j @Component public class JwtTokenUtil { /** * 盐 */ public static String SECRET = "79e7c61239681b8270162386e6daa53d1dd"; private static final long EXPIRATION = 28800000000L; /*生成token*/ public static <T> String generateToken(T t) { Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000); Date now = new Date(); Map<String, Object> map = new HashMap<>(); map.put("alg", "HS256"); map.put("typ", "JWT"); JWTCreator.Builder token = JWT.create() .withHeader(map) .withExpiresAt(expireDate) .withIssuedAt(now) .withNotBefore(now); if (t instanceof Map) { ((Map) t).forEach((k, v) -> token.withClaim(k + "", v + "")); } else { BeanUtil.beanToMap(t).forEach((x, y) -> token.withClaim(x, y + "")); } return token.sign(Algorithm.HMAC256(SECRET)); } /*解析token*/ public static <T> T parseToken(String token, Class<T> aclass) { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); DecodedJWT jwt = verifier.verify(token); Map<String, Claim> claims = jwt.getClaims(); HashMap<String, Object> hashMap = new HashMap<>(); claims.forEach((k, v) -> hashMap.put(k, v.asString())); T t = BeanUtil.mapToBean(hashMap, aclass, false, CopyOptions.create()); log.info("解析Token的内容:" + t); return t; } /*解析token*/ public static <T> T parseToken001(String token, Class<T> aclass) { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); DecodedJWT jwt = verifier.verify(token); Map<String, Claim> claims = jwt.getClaims(); String string = claims.get("loginId").asString(); T t = JSON.parseObject(string, aclass); log.info("解析Token的内容:" + t); return t; } }
支持跨域(可选)
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; /** * 跨域过滤器 * @author * */ @Component public class CorsFilter implements Filter { static final String OPTIONS = "OPTIONS"; @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String origin = request.getHeader("Origin"); // 获得客户端domain if(origin == null) { origin = request.getHeader("Referer"); } response.setHeader("Access-Control-Allow-Origin", origin); // 允许指定域访问跨域资源 response.setHeader("Access-Control-Allow-Credentials", "true"); // 允许客户端携带跨域cookie,此时origin值不能为“*”,只能为指定单一域名 response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken"); // 允许的header参数 // response.setHeader("Access-Control-Allow-Headers", "*"); // 允许的header参数 // 如果是预检请求,直接返回 if(OPTIONS.equals(request.getMethod())) { System.out.println("=======================浏览器发来了OPTIONS预检请求=========="); response.getWriter().print(""); return; } //System.out.println("*********************************过滤器被使用**************************2233"); chain.doFilter(req, res); } @Override public void init(FilterConfig filterConfig) {} @Override public void destroy() {} }
-
工具类
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); } }
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/162633.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...