Commit dcdde36253e2ffe65da37eb9649efe04c88dc6d6

Authored by YunaiV
2 parents c0168a8b f94dd152

Merge remote-tracking branch 'origin/master-jdk17' into HEAD

Showing 29 changed files with 1112 additions and 2871 deletions
sql/dm/flowable-patch-6.8.0/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/DmFlowableConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.config;
  2 +
  3 +import org.flowable.common.engine.impl.AbstractEngineConfiguration;
  4 +import org.springframework.beans.factory.config.BeanPostProcessor;
  5 +import org.springframework.context.annotation.Bean;
  6 +import org.springframework.context.annotation.Configuration;
  7 +
  8 +import java.sql.Connection;
  9 +import java.sql.SQLException;
  10 +
  11 +/**
  12 + * 达梦数据库的 Flowable 配置
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Configuration(proxyBeanMethods = false)
  17 +public class DmFlowableConfiguration {
  18 +
  19 + /**
  20 + * 在 Spring 管理的各个 Flowable 引擎初始化前设置达梦方言与插入策略
  21 + */
  22 + @Bean
  23 + public static BeanPostProcessor dmEngineConfigurationBeanPostProcessor() {
  24 + return new BeanPostProcessor() {
  25 +
  26 + @Override
  27 + public Object postProcessBeforeInitialization(Object bean, String beanName) {
  28 + if (!(bean instanceof AbstractEngineConfiguration)) {
  29 + return bean;
  30 + }
  31 + AbstractEngineConfiguration configuration = (AbstractEngineConfiguration) bean;
  32 + if (configuration.getDataSource() == null) {
  33 + return bean;
  34 + }
  35 + // 在各个 Spring 引擎初始化前处理,避免子引擎重新开启批量插入;使用实际数据源识别达梦
  36 + try (Connection connection = configuration.getDataSource().getConnection()) {
  37 + if ("DM DBMS".equalsIgnoreCase(connection.getMetaData().getDatabaseProductName())) {
  38 + configuration.setDatabaseType("oracle");
  39 + // 特殊:达梦普通用户执行 Oracle INSERT ALL 时可能访问 SYS.SYSDUAL2,改为普通插入,避免扩大数据库权限
  40 + configuration.setBulkInsertEnabled(false);
  41 + }
  42 + } catch (SQLException e) {
  43 + throw new IllegalStateException("读取 Flowable 数据库类型失败", e);
  44 + }
  45 + return bean;
  46 + }
  47 + };
  48 + }
  49 +
  50 +}
... ...
sql/dm/flowable-patch-6.8.0/src/main/java/liquibase/database/core/DmDatabase.java 0 → 100644
  1 +package liquibase.database.core;
  2 +
  3 +import liquibase.database.DatabaseConnection;
  4 +import liquibase.exception.DatabaseException;
  5 +
  6 +/**
  7 + * 达梦数据库的 Liquibase 适配,复用 Oracle 的字段类型与建表语法
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +public class DmDatabase extends OracleDatabase {
  12 +
  13 + @Override
  14 + public String getShortName() {
  15 + // 独立注册达梦,避免与 Oracle 的 SPI 注册键及优先级冲突
  16 + return "dm";
  17 + }
  18 +
  19 + @Override
  20 + protected String getDefaultDatabaseProductName() {
  21 + return "DM DBMS";
  22 + }
  23 +
  24 + @Override
  25 + public boolean isCorrectDatabaseImplementation(DatabaseConnection connection) throws DatabaseException {
  26 + return "DM DBMS".equalsIgnoreCase(connection.getDatabaseProductName());
  27 + }
  28 +
  29 + @Override
  30 + public String getDefaultDriver(String url) {
  31 + return url != null && url.startsWith("jdbc:dm:") ? "dm.jdbc.driver.DmDriver" : null;
  32 + }
  33 +
  34 + @Override
  35 + public Integer getDefaultPort() {
  36 + return 5236;
  37 + }
  38 +
  39 + @Override
  40 + public int getDatabaseMinorVersion() throws DatabaseException {
  41 + // 特殊:部分达梦驱动返回非数字次版本号,避免 Oracle 版本判断时解析失败
  42 + try {
  43 + return super.getDatabaseMinorVersion();
  44 + } catch (DatabaseException e) {
  45 + return 0;
  46 + }
  47 + }
  48 +
  49 +}
... ...
sql/dm/flowable-patch-6.8.0/src/main/resources/META-INF/services/liquibase.database.Database 0 → 100644
  1 +liquibase.database.core.DmDatabase
... ...
sql/dm/flowable-patch-8.0.0/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/DmFlowableConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.config;
  2 +
  3 +import org.flowable.common.engine.impl.AbstractEngineConfiguration;
  4 +import org.springframework.beans.factory.config.BeanPostProcessor;
  5 +import org.springframework.context.annotation.Bean;
  6 +import org.springframework.context.annotation.Configuration;
  7 +
  8 +import java.sql.Connection;
  9 +import java.sql.SQLException;
  10 +
  11 +/**
  12 + * 达梦数据库的 Flowable 配置
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Configuration(proxyBeanMethods = false)
  17 +public class DmFlowableConfiguration {
  18 +
  19 + /**
  20 + * 在 Spring 管理的各个 Flowable 引擎初始化前设置达梦方言与插入策略
  21 + */
  22 + @Bean
  23 + public static BeanPostProcessor dmEngineConfigurationBeanPostProcessor() {
  24 + return new BeanPostProcessor() {
  25 +
  26 + @Override
  27 + public Object postProcessBeforeInitialization(Object bean, String beanName) {
  28 + if (!(bean instanceof AbstractEngineConfiguration)) {
  29 + return bean;
  30 + }
  31 + AbstractEngineConfiguration configuration = (AbstractEngineConfiguration) bean;
  32 + if (configuration.getDataSource() == null) {
  33 + return bean;
  34 + }
  35 + // 在各个 Spring 引擎初始化前处理,避免子引擎重新开启批量插入;使用实际数据源识别达梦
  36 + try (Connection connection = configuration.getDataSource().getConnection()) {
  37 + if ("DM DBMS".equalsIgnoreCase(connection.getMetaData().getDatabaseProductName())) {
  38 + configuration.setDatabaseType("oracle");
  39 + // 特殊:达梦普通用户执行 Oracle INSERT ALL 时可能访问 SYS.SYSDUAL2,改为普通插入,避免扩大数据库权限
  40 + configuration.setBulkInsertEnabled(false);
  41 + }
  42 + } catch (SQLException e) {
  43 + throw new IllegalStateException("读取 Flowable 数据库类型失败", e);
  44 + }
  45 + return bean;
  46 + }
  47 + };
  48 + }
  49 +
  50 +}
... ...
sql/dm/flowable-patch-tests/README.md 0 → 100644
  1 +# Flowable 达梦补丁回归测试
  2 +
  3 +本目录是独立的 Maven 测试工程,直接编译同级 `flowable-patch-${flowable.version}` 补丁目录的源码,验证补丁本身。它不加入主工程模块,不参与业务默认构建,也不要复制到业务模块。
  4 +
  5 +补丁安装请阅读项目文档《工作流(达梦适配)》,本文只说明如何测试。
  6 +
  7 +## 1. 版本矩阵
  8 +
  9 +| Profile | JDK | Spring Boot | Flowable | 被测补丁 |
  10 +| --- | --- | --- | --- | --- |
  11 +| `jdk8` | JDK8 | 2.7.18 | 6.8.0 | `flowable-patch-6.8.0` |
  12 +| 默认 | JDK17 或 JDK25 | 3.5.15 | 8.0.0 | `flowable-patch-8.0.0` |
  13 +| `sb4` | JDK25 | 4.1.1 | 8.0.0 | `flowable-patch-8.0.0` |
  14 +
  15 +各组合的编译输出位于 `target/<Spring Boot 版本>-<Flowable 版本>`,互不覆盖。
  16 +
  17 +## 2. 准备数据库
  18 +
  19 +先创建独立的非生产测试账号,只授予该测试 schema 的建表权限,**不要使用生产库**。6.8.0 与 8.0.0 请使用不同账号/schema 分别测试,不能在同一工作流 schema 混跑两个 Flowable 版本。
  20 +
  21 +通过环境变量指定数据库,密码由本地环境提供,不写入 Git:
  22 +
  23 +```bash
  24 +export DM_JDBC_URL='jdbc:dm://127.0.0.1:15236'
  25 +export DM_JDBC_USER='FLOWABLE_TEST'
  26 +export DM_JDBC_PASSWORD='本地测试密码'
  27 +```
  28 +
  29 +## 3. 执行测试
  30 +
  31 +在 `ruoyi-vue-pro` 仓库根目录(包含 `sql/` 目录的一级)执行:
  32 +
  33 +```bash
  34 +# 使用 JDK8:Spring Boot 2.7.18 + Flowable 6.8.0
  35 +mvn -f sql/dm/flowable-patch-tests/pom.xml -Pjdk8 test
  36 +
  37 +# 使用 JDK17 或 JDK25:Spring Boot 3.5.15 + Flowable 8.0.0
  38 +mvn -f sql/dm/flowable-patch-tests/pom.xml test
  39 +
  40 +# 使用 JDK25:Spring Boot 4.1.1 + Flowable 8.0.0
  41 +mvn -f sql/dm/flowable-patch-tests/pom.xml -Psb4 test
  42 +```
  43 +
  44 +未配置 `DM_JDBC_URL` 时,只运行基于 H2 的非达梦检查(其它数据库不受补丁影响;6.8.0 另检查 SPI 注册顺序),真实达梦用例会在 Maven 输出中显示为 `Skipped`。此时**不能据此认定达梦适配通过**;验收时应确认真实达梦用例没有被跳过。
  45 +
  46 +## 4. 测试覆盖与边界
  47 +
  48 +覆盖 Spring Boot 自动装配(不手工关闭应用级 Liquibase)、首次建表与保持 `flowable.database-schema-update: true` 的重复启动、流程部署/发起/两级审批、退回与拒绝、取消、长文本和布尔变量、历史与分页、事务回滚、并行网关、定时 Job 执行、DMN 建表与决策,以及自动装配后一次部署多个 DMN/事件定义。6.8.0 另覆盖 Oracle/DM 两种 SPI 注册顺序,以及 DM 元数据与布尔类型识别。
  49 +
  50 +不包含完整芋道 Web 服务(微服务的网关、注册中心等)及前端浏览器验收,也不代表已验证所有流程模型、并发性能、跨版本历史库升级,或所有第三方 Flowable 用法。补丁合并到业务模块后,仍需在实际项目中验证启动及流程功能。
  51 +
  52 +## 5. 实现说明
  53 +
  54 +- 两个版本的 `DmFlowableConfiguration` 保持一致。通过 Spring `BeanPostProcessor`,在各个 Flowable 引擎配置 Bean 初始化前,按引擎实际连接的 JDBC 元数据识别达梦,设置工作流 Oracle 方言及普通插入,覆盖主引擎和 Spring 事件/DMN 子引擎。不修改 Flowable 原类,不替换全局 MyBatis `DatabaseIdProvider`,不依赖数据源名称或 `yudao.dameng.enabled` 开关。
  55 +- 对达梦关闭 `INSERT ALL` 批量插入,避免部分 DM8 环境下普通用户访问 `SYS.SYSDUAL2` 失败,无需给业务账号授予 DBA。仅关闭主引擎不够,子引擎会将自己的批量实体重新注册到共享 SQL 工厂。代价是批量写入吞吐可能降低,需按实际负载压测。
  56 +- 补丁只处理 Spring 管理的 Flowable 配置 Bean。手动 `new` 的非 Bean 配置、独立接入的其它引擎不会被自动覆盖,需要自行设置方言及插入策略。
  57 +- 仅 6.8.0 使用 Liquibase SPI 注册 `DmDatabase extends OracleDatabase`,复用 Oracle 的字段、布尔值及 schema 识别逻辑,不覆盖 `BooleanType`。注册短名为独立的 `dm`,避免与原生 Oracle 实现竞争同一个注册键;Flowable 方言仍为 `oracle`,两者是不同机制。Flowable 6.8.0 自带的环境后处理器默认关闭 Spring Boot 应用级 Liquibase,内部事件/DMN 的 Liquibase 建表仍正常执行。
  58 +- 8.0.0 的事件/DMN 已改用 SQL 脚本,补丁仅包含 Spring 配置,不引入 Liquibase。若额外加入 Liquibase 依赖,Boot 3 会默认查找应用级 changelog 导致启动失败;不要用 `spring.liquibase.enabled=false` 掩盖该问题。
  59 +- DM 的 `COMPATIBLE_MODE` 是数据库配置,`oracle` 是 Flowable 方言,两者不是同一设置。不要在已有库上直接更换方言、重建表或混用两个 Flowable 版本;升级现有工作流数据库须另行备份、验证官方升级脚本,补丁不是升级脚本。
... ...
sql/dm/flowable-patch-tests/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>dm-flowable-patch-tests</artifactId>
  8 + <version>1.0-SNAPSHOT</version>
  9 + <properties>
  10 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  11 + <flowable.version>8.0.0</flowable.version>
  12 + <spring.boot.version>3.5.15</spring.boot.version>
  13 + <maven.compiler.release>17</maven.compiler.release>
  14 + </properties>
  15 + <dependencyManagement>
  16 + <dependencies>
  17 + <dependency>
  18 + <groupId>org.springframework.boot</groupId>
  19 + <artifactId>spring-boot-dependencies</artifactId>
  20 + <version>${spring.boot.version}</version>
  21 + <type>pom</type>
  22 + <scope>import</scope>
  23 + </dependency>
  24 + </dependencies>
  25 + </dependencyManagement>
  26 + <dependencies>
  27 + <dependency>
  28 + <groupId>org.flowable</groupId>
  29 + <artifactId>flowable-spring-boot-starter-process</artifactId>
  30 + <version>${flowable.version}</version>
  31 + </dependency>
  32 + <dependency>
  33 + <groupId>org.flowable</groupId>
  34 + <artifactId>flowable-dmn-spring-configurator</artifactId>
  35 + <version>${flowable.version}</version>
  36 + </dependency>
  37 + <dependency>
  38 + <groupId>com.dameng</groupId>
  39 + <artifactId>DmJdbcDriver18</artifactId>
  40 + <version>8.1.3.140</version>
  41 + <scope>test</scope>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>com.h2database</groupId>
  45 + <artifactId>h2</artifactId>
  46 + <scope>test</scope>
  47 + </dependency>
  48 + <dependency>
  49 + <groupId>org.junit.jupiter</groupId>
  50 + <artifactId>junit-jupiter</artifactId>
  51 + <scope>test</scope>
  52 + </dependency>
  53 + </dependencies>
  54 + <build>
  55 + <directory>${project.basedir}/target/${spring.boot.version}-${flowable.version}</directory>
  56 + <sourceDirectory>../flowable-patch-${flowable.version}/src/main/java</sourceDirectory>
  57 + <resources>
  58 + <resource>
  59 + <directory>../flowable-patch-${flowable.version}/src/main/resources</directory>
  60 + </resource>
  61 + </resources>
  62 + <plugins>
  63 + <plugin>
  64 + <groupId>org.apache.maven.plugins</groupId>
  65 + <artifactId>maven-compiler-plugin</artifactId>
  66 + <version>3.13.0</version>
  67 + </plugin>
  68 + <plugin>
  69 + <groupId>org.apache.maven.plugins</groupId>
  70 + <artifactId>maven-surefire-plugin</artifactId>
  71 + <version>3.5.3</version>
  72 + </plugin>
  73 + </plugins>
  74 + </build>
  75 + <profiles>
  76 + <profile>
  77 + <id>jdk8</id>
  78 + <properties>
  79 + <flowable.version>6.8.0</flowable.version>
  80 + <spring.boot.version>2.7.18</spring.boot.version>
  81 + <maven.compiler.release>8</maven.compiler.release>
  82 + </properties>
  83 + <dependencies>
  84 + <dependency>
  85 + <groupId>org.liquibase</groupId>
  86 + <artifactId>liquibase-core</artifactId>
  87 + </dependency>
  88 + </dependencies>
  89 + <build>
  90 + <plugins>
  91 + <plugin>
  92 + <groupId>org.codehaus.mojo</groupId>
  93 + <artifactId>build-helper-maven-plugin</artifactId>
  94 + <version>3.6.1</version>
  95 + <executions>
  96 + <execution>
  97 + <id>liquibase-patch-tests</id>
  98 + <phase>generate-test-sources</phase>
  99 + <goals><goal>add-test-source</goal></goals>
  100 + <configuration>
  101 + <sources><source>src/test/liquibase</source></sources>
  102 + </configuration>
  103 + </execution>
  104 + </executions>
  105 + </plugin>
  106 + </plugins>
  107 + </build>
  108 + </profile>
  109 + <profile>
  110 + <id>sb4</id>
  111 + <properties>
  112 + <spring.boot.version>4.1.1</spring.boot.version>
  113 + <maven.compiler.release>25</maven.compiler.release>
  114 + </properties>
  115 + </profile>
  116 + </profiles>
  117 +</project>
... ...
sql/dm/flowable-patch-tests/src/test/java/cn/iocoder/yudao/module/bpm/DmFlowablePatchTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm;
  2 +
  3 +import cn.iocoder.yudao.module.bpm.framework.flowable.config.DmFlowableConfiguration;
  4 +import org.flowable.engine.ProcessEngine;
  5 +import org.flowable.engine.history.HistoricProcessInstance;
  6 +import org.flowable.engine.runtime.ProcessInstance;
  7 +import org.flowable.dmn.engine.DmnEngine;
  8 +import org.flowable.dmn.spring.SpringDmnEngineConfiguration;
  9 +import org.flowable.eventregistry.impl.EventRegistryEngine;
  10 +import org.flowable.eventregistry.api.EventDeployment;
  11 +import org.flowable.spring.SpringProcessEngineConfiguration;
  12 +import org.flowable.task.api.Task;
  13 +import org.junit.jupiter.api.Test;
  14 +import org.springframework.boot.WebApplicationType;
  15 +import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  16 +import org.springframework.boot.builder.SpringApplicationBuilder;
  17 +import org.springframework.context.ConfigurableApplicationContext;
  18 +import org.springframework.context.annotation.Bean;
  19 +import org.springframework.context.annotation.Configuration;
  20 +import org.springframework.context.annotation.Import;
  21 +import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  22 +import org.springframework.jdbc.datasource.DriverManagerDataSource;
  23 +import org.springframework.transaction.support.TransactionTemplate;
  24 +
  25 +import javax.sql.DataSource;
  26 +import java.sql.Connection;
  27 +import java.util.Collections;
  28 +import java.util.List;
  29 +import java.util.Map;
  30 +import java.util.UUID;
  31 +import java.nio.charset.StandardCharsets;
  32 +import java.io.ByteArrayOutputStream;
  33 +import java.io.InputStream;
  34 +
  35 +import static org.junit.jupiter.api.Assertions.*;
  36 +import static org.junit.jupiter.api.Assumptions.assumeTrue;
  37 +
  38 +/**
  39 + * 达梦补丁的版本兼容与真实数据库回归测试
  40 + *
  41 + * @author 芋道源码
  42 + */
  43 +class DmFlowablePatchTest {
  44 +
  45 + @Test
  46 + void testOtherDatabaseUnchanged() throws Exception {
  47 + DriverManagerDataSource dataSource = new DriverManagerDataSource("jdbc:h2:mem:dm_patch_regression", "sa", "");
  48 + SpringProcessEngineConfiguration configuration = configuration(dataSource);
  49 + configureDm(configuration);
  50 + assertNull(configuration.getDatabaseType());
  51 + assertTrue(configuration.isBulkInsertEnabled());
  52 + }
  53 +
  54 + @Test
  55 + void testDmSchemaAndProcess() throws Exception {
  56 + DataSource dataSource = dmDataSource();
  57 + try (Connection connection = dataSource.getConnection()) {
  58 + assertEquals("DM DBMS", connection.getMetaData().getDatabaseProductName());
  59 + }
  60 + String instanceId;
  61 + SpringProcessEngineConfiguration first = configuration(dataSource);
  62 + first.setDatabaseSchemaUpdate("true");
  63 + configureDm(first);
  64 + assertEquals("oracle", first.getDatabaseType());
  65 + ProcessEngine engine = first.buildProcessEngine();
  66 + try {
  67 + engine.getRepositoryService().createDeployment().addClasspathResource("approval.bpmn20.xml").deploy();
  68 + ProcessInstance instance = engine.getRuntimeService().startProcessInstanceByKey("dmApproval", "dm-test",
  69 + Collections.<String, Object>singletonMap("amount", 100));
  70 + instanceId = instance.getId();
  71 + Task task = engine.getTaskService().createTaskQuery().processInstanceId(instanceId).singleResult();
  72 + assertEquals("审批一", task.getName());
  73 + engine.getTaskService().setAssignee(task.getId(), "1");
  74 + engine.getTaskService().complete(task.getId());
  75 + assertEquals("审批二", engine.getTaskService().createTaskQuery().processInstanceId(instanceId).singleResult().getName());
  76 + } finally {
  77 + engine.close();
  78 + }
  79 + // 再次开启 schema-update,验证已存在的工作流表不会重复创建
  80 + SpringProcessEngineConfiguration second = configuration(dataSource);
  81 + second.setDatabaseSchemaUpdate("true");
  82 + configureDm(second);
  83 + engine = second.buildProcessEngine();
  84 + try {
  85 + Task task = engine.getTaskService().createTaskQuery().processInstanceId(instanceId).singleResult();
  86 + engine.getTaskService().complete(task.getId());
  87 + assertNull(engine.getRuntimeService().createProcessInstanceQuery().processInstanceId(instanceId).singleResult());
  88 + HistoricProcessInstance history = engine.getHistoryService().createHistoricProcessInstanceQuery()
  89 + .processInstanceId(instanceId).singleResult();
  90 + assertNotNull(history.getEndTime());
  91 + assertEquals(2, engine.getHistoryService().createHistoricTaskInstanceQuery().processInstanceId(instanceId).finished().count());
  92 + assertEquals(1, engine.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey("dmApproval").listPage(0, 1).size());
  93 + } finally {
  94 + engine.close();
  95 + }
  96 + }
  97 +
  98 + @Test
  99 + void testDmReturnAndReject() {
  100 + ProcessEngine engine = dmEngine();
  101 + try {
  102 + engine.getRepositoryService().createDeployment().addClasspathResource("approval.bpmn20.xml").deploy();
  103 + ProcessInstance instance = engine.getRuntimeService().startProcessInstanceByKey("dmApproval");
  104 + Task task = engine.getTaskService().createTaskQuery().processInstanceId(instance.getId()).singleResult();
  105 + engine.getTaskService().complete(task.getId());
  106 + engine.getRuntimeService().createChangeActivityStateBuilder().processInstanceId(instance.getId())
  107 + .moveActivityIdTo("second", "first").changeState();
  108 + assertEquals("审批一", engine.getTaskService().createTaskQuery().processInstanceId(instance.getId()).singleResult().getName());
  109 + engine.getRuntimeService().deleteProcessInstance(instance.getId(), "审批拒绝");
  110 + assertEquals("审批拒绝", engine.getHistoryService().createHistoricProcessInstanceQuery()
  111 + .processInstanceId(instance.getId()).singleResult().getDeleteReason());
  112 + } finally {
  113 + engine.close();
  114 + }
  115 + }
  116 +
  117 + @Test
  118 + void testDmCancelAndVariables() {
  119 + ProcessEngine engine = dmEngine();
  120 + try {
  121 + engine.getRepositoryService().createDeployment().addClasspathResource("approval.bpmn20.xml").deploy();
  122 + String longText = String.join("", Collections.nCopies(3000, "达梦🙂"));
  123 + ProcessInstance instance = engine.getRuntimeService().startProcessInstanceByKey("dmApproval", "cancel-test",
  124 + Collections.<String, Object>singletonMap("longText", longText));
  125 + engine.getRuntimeService().setVariable(instance.getId(), "approved", true);
  126 + assertEquals(longText, engine.getRuntimeService().getVariable(instance.getId(), "longText"));
  127 + assertEquals(true, engine.getRuntimeService().getVariable(instance.getId(), "approved"));
  128 + engine.getRuntimeService().deleteProcessInstance(instance.getId(), "发起人取消");
  129 + assertEquals("发起人取消", engine.getHistoryService().createHistoricProcessInstanceQuery()
  130 + .processInstanceBusinessKey("cancel-test").orderByProcessInstanceStartTime().desc().listPage(0, 1).get(0).getDeleteReason());
  131 + assertEquals(longText, engine.getHistoryService().createHistoricVariableInstanceQuery()
  132 + .processInstanceId(instance.getId()).variableName("longText").singleResult().getValue());
  133 + } finally {
  134 + engine.close();
  135 + }
  136 + }
  137 +
  138 + @Test
  139 + void testDmParallelGateway() {
  140 + ProcessEngine engine = dmEngine();
  141 + try {
  142 + engine.getRepositoryService().createDeployment().addClasspathResource("parallel.bpmn20.xml").deploy();
  143 + ProcessInstance instance = engine.getRuntimeService().startProcessInstanceByKey("dmParallel");
  144 + List<Task> tasks = engine.getTaskService().createTaskQuery().processInstanceId(instance.getId()).list();
  145 + assertEquals(2, tasks.size());
  146 + engine.getTaskService().complete(tasks.get(0).getId());
  147 + assertEquals(1, engine.getRuntimeService().createProcessInstanceQuery().processInstanceId(instance.getId()).count());
  148 + engine.getTaskService().complete(tasks.get(1).getId());
  149 + assertEquals(0, engine.getRuntimeService().createProcessInstanceQuery().processInstanceId(instance.getId()).count());
  150 + } finally {
  151 + engine.close();
  152 + }
  153 + }
  154 +
  155 + @Test
  156 + void testDmTimer() {
  157 + ProcessEngine engine = dmEngine();
  158 + try {
  159 + engine.getRepositoryService().createDeployment().addClasspathResource("timer.bpmn20.xml").deploy();
  160 + ProcessInstance instance = engine.getRuntimeService().startProcessInstanceByKey("dmTimer");
  161 + String timerId = engine.getManagementService().createTimerJobQuery().processInstanceId(instance.getId()).singleResult().getId();
  162 + String jobId = engine.getManagementService().moveTimerToExecutableJob(timerId).getId();
  163 + engine.getManagementService().executeJob(jobId);
  164 + assertEquals(0, engine.getManagementService().createTimerJobQuery().processInstanceId(instance.getId()).count());
  165 + assertEquals("定时后审批", engine.getTaskService().createTaskQuery().processInstanceId(instance.getId()).singleResult().getName());
  166 + engine.getRuntimeService().deleteProcessInstance(instance.getId(), "测试结束");
  167 + } finally {
  168 + engine.close();
  169 + }
  170 + }
  171 +
  172 + @Test
  173 + void testDmDmnSchemaAndDecision() {
  174 + DataSource dataSource = dmDataSource();
  175 + SpringDmnEngineConfiguration configuration = new SpringDmnEngineConfiguration();
  176 + configuration.setDataSource(dataSource);
  177 + configuration.setTransactionManager(new DataSourceTransactionManager(dataSource));
  178 + configureDm(configuration);
  179 + configuration.setDatabaseSchemaUpdate("true");
  180 + DmnEngine engine = configuration.buildDmnEngine();
  181 + try {
  182 + engine.getDmnRepositoryService().createDeployment().addClasspathResource("amount.dmn").deploy();
  183 + Map<String, Object> result = engine.getDmnDecisionService().createExecuteDecisionBuilder()
  184 + .decisionKey("dmAmount").variable("amount", 100).executeWithSingleResult();
  185 + assertEquals("通过", result.get("result"));
  186 + } finally {
  187 + engine.close();
  188 + }
  189 + // DMN 在 6.8.0 使用 Liquibase,重复启动必须能识别已存在的 changelog 表
  190 + configuration = new SpringDmnEngineConfiguration();
  191 + configuration.setDataSource(dataSource);
  192 + configuration.setTransactionManager(new DataSourceTransactionManager(dataSource));
  193 + configureDm(configuration);
  194 + configuration.setDatabaseSchemaUpdate("true");
  195 + engine = configuration.buildDmnEngine();
  196 + engine.close();
  197 + }
  198 +
  199 + @Test
  200 + void testBootAutoConfiguration() {
  201 + dmDataSource();
  202 + try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestApplication.class)
  203 + .web(WebApplicationType.NONE)
  204 + .properties("flowable.database-schema-update=true", "flowable.async-executor-activate=false",
  205 + "logging.level.root=WARN", "spring.main.banner-mode=off")
  206 + .run()) {
  207 + ProcessEngine engine = context.getBean(ProcessEngine.class);
  208 + assertEquals("oracle", engine.getProcessEngineConfiguration().getDatabaseType());
  209 + assertFalse(((SpringProcessEngineConfiguration) engine.getProcessEngineConfiguration()).isBulkInsertEnabled());
  210 + assertNotNull(engine.getRepositoryService().createDeploymentQuery().listPage(0, 1));
  211 + DmnEngine dmn = context.getBean(DmnEngine.class);
  212 + assertEquals("oracle", dmn.getDmnEngineConfiguration().getDatabaseType());
  213 + assertFalse(dmn.getDmnEngineConfiguration().isBulkInsertEnabled());
  214 + assertEquals("通过", dmn.getDmnDecisionService().createExecuteDecisionBuilder()
  215 + .decisionKey("dmAutoTwo").variable("amount", 100).executeWithSingleResult().get("result"));
  216 + String model = resourceText("/amount.dmn");
  217 + dmn.getDmnRepositoryService().createDeployment().addString("one.dmn", model)
  218 + .addString("two.dmn", model.replace("dmAmount", "dmAmountTwo")).deploy();
  219 + assertEquals("通过", dmn.getDmnDecisionService().createExecuteDecisionBuilder()
  220 + .decisionKey("dmAmountTwo").variable("amount", 100).executeWithSingleResult().get("result"));
  221 + EventRegistryEngine event = context.getBean(EventRegistryEngine.class);
  222 + assertEquals("oracle", event.getEventRegistryEngineConfiguration().getDatabaseType());
  223 + assertFalse(event.getEventRegistryEngineConfiguration().isBulkInsertEnabled());
  224 + assertTrue(event.getEventRepositoryService().createEventDefinitionQuery().eventDefinitionKey("dmAutoEventOne").count() > 0);
  225 + assertTrue(event.getEventRepositoryService().createEventDefinitionQuery().eventDefinitionKey("dmAutoEventTwo").count() > 0);
  226 + EventDeployment deployment = event.getEventRepositoryService().createDeployment()
  227 + .addString("one.event", "{\"key\":\"dmEventOne\",\"name\":\"One\",\"payload\":[]}")
  228 + .addString("two.event", "{\"key\":\"dmEventTwo\",\"name\":\"Two\",\"payload\":[]}").deploy();
  229 + assertEquals(2, event.getEventRepositoryService().createEventDefinitionQuery().deploymentId(deployment.getId()).count());
  230 + }
  231 + }
  232 +
  233 + private ProcessEngine dmEngine() {
  234 + SpringProcessEngineConfiguration configuration = configuration(dmDataSource());
  235 + configuration.setDatabaseSchemaUpdate("true");
  236 + configureDm(configuration);
  237 + return configuration.buildProcessEngine();
  238 + }
  239 +
  240 + private static DataSource dmDataSource() {
  241 + assumeTrue(System.getenv("DM_JDBC_URL") != null, "设置 DM_JDBC_URL 后执行真实达梦测试");
  242 + return new DriverManagerDataSource(System.getenv("DM_JDBC_URL"),
  243 + System.getenv("DM_JDBC_USER"), System.getenv("DM_JDBC_PASSWORD"));
  244 + }
  245 +
  246 + @Test
  247 + void testDmTransactionRollback() {
  248 + ProcessEngine engine = dmEngine();
  249 + try {
  250 + engine.getRepositoryService().createDeployment().addClasspathResource("approval.bpmn20.xml").deploy();
  251 + String businessKey = UUID.randomUUID().toString();
  252 + SpringProcessEngineConfiguration configuration = (SpringProcessEngineConfiguration) engine.getProcessEngineConfiguration();
  253 + TransactionTemplate transaction = new TransactionTemplate(configuration.getTransactionManager());
  254 + assertThrows(IllegalStateException.class, () -> transaction.execute(status -> {
  255 + engine.getRuntimeService().startProcessInstanceByKey("dmApproval", businessKey);
  256 + throw new IllegalStateException("回滚测试");
  257 + }));
  258 + assertEquals(0, engine.getRuntimeService().createProcessInstanceQuery().processInstanceBusinessKey(businessKey).count());
  259 + assertEquals(0, engine.getHistoryService().createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey).count());
  260 + } finally {
  261 + engine.close();
  262 + }
  263 + }
  264 +
  265 + private void configureDm(Object configuration) {
  266 + DmFlowableConfiguration.dmEngineConfigurationBeanPostProcessor().postProcessBeforeInitialization(configuration, "testEngineConfiguration");
  267 + }
  268 +
  269 + private String resourceText(String name) {
  270 + try (InputStream input = getClass().getResourceAsStream(name);
  271 + ByteArrayOutputStream output = new ByteArrayOutputStream()) {
  272 + byte[] buffer = new byte[1024];
  273 + int count;
  274 + while ((count = input.read(buffer)) != -1) {
  275 + output.write(buffer, 0, count);
  276 + }
  277 + return new String(output.toByteArray(), StandardCharsets.UTF_8);
  278 + } catch (java.io.IOException e) {
  279 + throw new IllegalStateException(e);
  280 + }
  281 + }
  282 +
  283 + private SpringProcessEngineConfiguration configuration(DataSource dataSource) {
  284 + SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration();
  285 + configuration.setDataSource(dataSource);
  286 + configuration.setTransactionManager(new DataSourceTransactionManager(dataSource));
  287 + configuration.setAsyncExecutorActivate(false);
  288 + return configuration;
  289 + }
  290 +
  291 + @Configuration(proxyBeanMethods = false)
  292 + @EnableAutoConfiguration
  293 + @Import(DmFlowableConfiguration.class)
  294 + static class TestApplication {
  295 +
  296 + @Bean
  297 + public DataSource dataSource() {
  298 + return dmDataSource();
  299 + }
  300 +
  301 + }
  302 +
  303 +}
... ...
sql/dm/flowable-patch-tests/src/test/liquibase/cn/iocoder/yudao/module/bpm/DmDatabaseTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm;
  2 +
  3 +import liquibase.database.DatabaseConnection;
  4 +import liquibase.database.DatabaseFactory;
  5 +import liquibase.database.core.DmDatabase;
  6 +import liquibase.database.core.H2Database;
  7 +import liquibase.database.core.MySQLDatabase;
  8 +import liquibase.database.core.OracleDatabase;
  9 +import liquibase.database.jvm.JdbcConnection;
  10 +import liquibase.datatype.DataTypeFactory;
  11 +import liquibase.datatype.core.BooleanType;
  12 +import org.junit.jupiter.api.AfterEach;
  13 +import org.junit.jupiter.api.Test;
  14 +import org.springframework.jdbc.datasource.DriverManagerDataSource;
  15 +
  16 +import java.lang.reflect.Proxy;
  17 +import java.sql.Connection;
  18 +
  19 +import static org.junit.jupiter.api.Assertions.*;
  20 +import static org.junit.jupiter.api.Assumptions.assumeTrue;
  21 +
  22 +/**
  23 + * Flowable 6.8.0 的达梦 Liquibase SPI 测试
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +class DmDatabaseTest {
  28 +
  29 + @AfterEach
  30 + void tearDown() {
  31 + DatabaseFactory.reset();
  32 + }
  33 +
  34 + @Test
  35 + void testDriverAndOtherDatabases() throws Exception {
  36 + DmDatabase database = new DmDatabase();
  37 + assertEquals("dm", database.getShortName());
  38 + assertEquals("dm.jdbc.driver.DmDriver", database.getDefaultDriver("jdbc:dm://localhost:5236"));
  39 + assertNull(database.getDefaultDriver(null));
  40 + assertNull(database.getDefaultDriver("jdbc:mysql://localhost/test"));
  41 + assertEquals(5236, database.getDefaultPort());
  42 + DriverManagerDataSource dataSource = new DriverManagerDataSource("jdbc:h2:mem:dm_spi_regression", "sa", "");
  43 + try (Connection connection = dataSource.getConnection()) {
  44 + assertInstanceOf(H2Database.class, DatabaseFactory.getInstance()
  45 + .findCorrectDatabaseImplementation(new JdbcConnection(connection)));
  46 + }
  47 + assertEquals(new BooleanType().toDatabaseDataType(new MySQLDatabase()).toString(),
  48 + DataTypeFactory.getInstance().fromDescription("boolean", new MySQLDatabase())
  49 + .toDatabaseDataType(new MySQLDatabase()).toString());
  50 + }
  51 +
  52 + @Test
  53 + void testOracleAndDmRegistrationOrder() throws Exception {
  54 + for (boolean dmFirst : new boolean[]{true, false}) {
  55 + DatabaseFactory factory = DatabaseFactory.getInstance();
  56 + factory.clearRegistry();
  57 + factory.register(dmFirst ? new DmDatabase() : new OracleDatabase());
  58 + factory.register(dmFirst ? new OracleDatabase() : new DmDatabase());
  59 + assertInstanceOf(DmDatabase.class, factory.findCorrectDatabaseImplementation(metadata("DM DBMS")));
  60 + assertEquals(OracleDatabase.class, factory.findCorrectDatabaseImplementation(metadata("Oracle")).getClass());
  61 + }
  62 + }
  63 +
  64 + @Test
  65 + void testDmMetadataAndBooleanType() throws Exception {
  66 + assumeTrue(System.getenv("DM_JDBC_URL") != null, "设置 DM_JDBC_URL 后执行真实达梦测试");
  67 + DriverManagerDataSource dataSource = new DriverManagerDataSource(System.getenv("DM_JDBC_URL"),
  68 + System.getenv("DM_JDBC_USER"), System.getenv("DM_JDBC_PASSWORD"));
  69 + try (Connection connection = dataSource.getConnection()) {
  70 + liquibase.database.Database database = DatabaseFactory.getInstance()
  71 + .findCorrectDatabaseImplementation(new JdbcConnection(connection));
  72 + assertInstanceOf(DmDatabase.class, database);
  73 + assertEquals("NUMBER(1)", DataTypeFactory.getInstance().fromDescription("boolean", database)
  74 + .toDatabaseDataType(database).toString());
  75 + }
  76 + }
  77 +
  78 + private DatabaseConnection metadata(String product) {
  79 + return (DatabaseConnection) Proxy.newProxyInstance(getClass().getClassLoader(),
  80 + new Class[]{DatabaseConnection.class}, (proxy, method, arguments) -> {
  81 + if ("getDatabaseProductName".equals(method.getName())) return product;
  82 + if ("getConnectionUserName".equals(method.getName())) return "TEST_USER";
  83 + if ("getURL".equals(method.getName())) return "DM DBMS".equals(product) ? "jdbc:dm://test" : "jdbc:oracle:thin:@test";
  84 + if (method.getReturnType() == int.class) return 8;
  85 + if (method.getReturnType() == boolean.class) return false;
  86 + return null;
  87 + });
  88 + }
  89 +
  90 +}
... ...
sql/dm/flowable-patch-tests/src/test/resources/amount.dmn 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/"
  3 + id="dmRules" name="达梦决策测试" namespace="http://www.iocoder.cn/dm-test">
  4 + <decision id="dmAmount" name="金额审核">
  5 + <decisionTable id="amountTable" hitPolicy="FIRST">
  6 + <input id="inputAmount"><inputExpression id="amountExpression" typeRef="number"><text>amount</text></inputExpression></input>
  7 + <output id="outputResult" name="result" typeRef="string"/>
  8 + <rule id="accepted">
  9 + <inputEntry id="amountCondition"><text>&gt;= 0</text></inputEntry>
  10 + <outputEntry id="resultValue"><text>"通过"</text></outputEntry>
  11 + </rule>
  12 + </decisionTable>
  13 + </decision>
  14 +</definitions>
... ...
sql/dm/flowable-patch-tests/src/test/resources/approval.bpmn20.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  3 + targetNamespace="http://www.iocoder.cn/dm-test">
  4 + <process id="dmApproval" name="达梦审批回归" isExecutable="true">
  5 + <startEvent id="start"/>
  6 + <userTask id="first" name="审批一"/>
  7 + <userTask id="second" name="审批二"/>
  8 + <endEvent id="end"/>
  9 + <sequenceFlow id="f1" sourceRef="start" targetRef="first"/>
  10 + <sequenceFlow id="f2" sourceRef="first" targetRef="second"/>
  11 + <sequenceFlow id="f3" sourceRef="second" targetRef="end"/>
  12 + </process>
  13 +</definitions>
... ...
sql/dm/flowable-patch-tests/src/test/resources/dmn/auto.dmn 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/"
  3 + id="autoRules" name="自动部署测试" namespace="http://www.iocoder.cn/dm-test">
  4 + <decision id="dmAutoOne" name="自动决策一">
  5 + <decisionTable id="tableOne" hitPolicy="FIRST">
  6 + <input id="inputOne"><inputExpression id="expressionOne" typeRef="number"><text>amount</text></inputExpression></input>
  7 + <output id="outputOne" name="result" typeRef="string"/>
  8 + <rule id="ruleOne">
  9 + <inputEntry id="conditionOne"><text>&gt;= 0</text></inputEntry>
  10 + <outputEntry id="valueOne"><text>"通过"</text></outputEntry>
  11 + </rule>
  12 + </decisionTable>
  13 + </decision>
  14 + <decision id="dmAutoTwo" name="自动决策二">
  15 + <decisionTable id="tableTwo" hitPolicy="FIRST">
  16 + <input id="inputTwo"><inputExpression id="expressionTwo" typeRef="number"><text>amount</text></inputExpression></input>
  17 + <output id="outputTwo" name="result" typeRef="string"/>
  18 + <rule id="ruleTwo">
  19 + <inputEntry id="conditionTwo"><text>&gt;= 0</text></inputEntry>
  20 + <outputEntry id="valueTwo"><text>"通过"</text></outputEntry>
  21 + </rule>
  22 + </decisionTable>
  23 + </decision>
  24 +</definitions>
... ...
sql/dm/flowable-patch-tests/src/test/resources/eventregistry/one.event 0 → 100644
  1 +{"key":"dmAutoEventOne","name":"自动事件一","payload":[]}
... ...
sql/dm/flowable-patch-tests/src/test/resources/eventregistry/two.event 0 → 100644
  1 +{"key":"dmAutoEventTwo","name":"自动事件二","payload":[]}
... ...
sql/dm/flowable-patch-tests/src/test/resources/logback-test.xml 0 → 100644
  1 +<configuration>
  2 + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  3 + <encoder><pattern>%level %logger{40} - %msg%n</pattern></encoder>
  4 + </appender>
  5 + <root level="WARN"><appender-ref ref="STDOUT"/></root>
  6 +</configuration>
... ...
sql/dm/flowable-patch-tests/src/test/resources/parallel.bpmn20.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  3 + targetNamespace="http://www.iocoder.cn/dm-test">
  4 + <process id="dmParallel" isExecutable="true">
  5 + <startEvent id="start"/>
  6 + <parallelGateway id="fork"/>
  7 + <userTask id="left" name="并行审批一"/>
  8 + <userTask id="right" name="并行审批二"/>
  9 + <parallelGateway id="join"/>
  10 + <endEvent id="end"/>
  11 + <sequenceFlow id="f1" sourceRef="start" targetRef="fork"/>
  12 + <sequenceFlow id="f2" sourceRef="fork" targetRef="left"/>
  13 + <sequenceFlow id="f3" sourceRef="fork" targetRef="right"/>
  14 + <sequenceFlow id="f4" sourceRef="left" targetRef="join"/>
  15 + <sequenceFlow id="f5" sourceRef="right" targetRef="join"/>
  16 + <sequenceFlow id="f6" sourceRef="join" targetRef="end"/>
  17 + </process>
  18 +</definitions>
... ...
sql/dm/flowable-patch-tests/src/test/resources/timer.bpmn20.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  3 + targetNamespace="http://www.iocoder.cn/dm-test">
  4 + <process id="dmTimer" isExecutable="true">
  5 + <startEvent id="start"/>
  6 + <intermediateCatchEvent id="wait">
  7 + <timerEventDefinition><timeDuration>P1D</timeDuration></timerEventDefinition>
  8 + </intermediateCatchEvent>
  9 + <userTask id="approval" name="定时后审批"/>
  10 + <endEvent id="end"/>
  11 + <sequenceFlow id="f1" sourceRef="start" targetRef="wait"/>
  12 + <sequenceFlow id="f2" sourceRef="wait" targetRef="approval"/>
  13 + <sequenceFlow id="f3" sourceRef="approval" targetRef="end"/>
  14 + </process>
  15 +</definitions>
... ...
sql/dm/flowable-patch/src/main/java/liquibase/database/core/DmDatabase.java deleted
1   -package liquibase.database.core;
2   -
3   -import java.lang.reflect.Method;
4   -import java.sql.Connection;
5   -import java.sql.ResultSet;
6   -import java.sql.SQLException;
7   -import java.sql.Statement;
8   -import java.util.ArrayList;
9   -import java.util.Arrays;
10   -import java.util.HashSet;
11   -import java.util.List;
12   -import java.util.Locale;
13   -import java.util.Map;
14   -import java.util.Properties;
15   -import java.util.Set;
16   -import java.util.regex.Matcher;
17   -import java.util.regex.Pattern;
18   -import liquibase.CatalogAndSchema;
19   -import liquibase.Scope;
20   -import liquibase.database.AbstractJdbcDatabase;
21   -import liquibase.database.DatabaseConnection;
22   -import liquibase.database.OfflineConnection;
23   -import liquibase.database.jvm.JdbcConnection;
24   -import liquibase.exception.DatabaseException;
25   -import liquibase.exception.UnexpectedLiquibaseException;
26   -import liquibase.exception.ValidationErrors;
27   -import liquibase.executor.ExecutorService;
28   -import liquibase.statement.DatabaseFunction;
29   -import liquibase.statement.SequenceCurrentValueFunction;
30   -import liquibase.statement.SequenceNextValueFunction;
31   -import liquibase.statement.core.RawCallStatement;
32   -import liquibase.statement.core.RawSqlStatement;
33   -import liquibase.structure.DatabaseObject;
34   -import liquibase.structure.core.Catalog;
35   -import liquibase.structure.core.Index;
36   -import liquibase.structure.core.PrimaryKey;
37   -import liquibase.structure.core.Schema;
38   -import liquibase.util.JdbcUtils;
39   -import liquibase.util.StringUtil;
40   -
41   -public class DmDatabase extends AbstractJdbcDatabase {
42   - private static final String PRODUCT_NAME = "DM DBMS";
43   -
44   - @Override
45   - protected String getDefaultDatabaseProductName() {
46   - return PRODUCT_NAME;
47   - }
48   -
49   - /**
50   - * Is this AbstractDatabase subclass the correct one to use for the given connection.
51   - *
52   - * @param conn
53   - */
54   - @Override
55   - public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
56   - return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
57   - }
58   -
59   - /**
60   - * If this database understands the given url, return the default driver class name. Otherwise return null.
61   - *
62   - * @param url
63   - */
64   - @Override
65   - public String getDefaultDriver(String url) {
66   - if(url.startsWith("jdbc:dm")) {
67   - return "dm.jdbc.driver.DmDriver";
68   - }
69   -
70   - return null;
71   - }
72   -
73   - /**
74   - * Returns an all-lower-case short name of the product. Used for end-user selecting of database type
75   - * such as the DBMS precondition.
76   - */
77   - @Override
78   - public String getShortName() {
79   - return "dm";
80   - }
81   -
82   - @Override
83   - public Integer getDefaultPort() {
84   - return 5236;
85   - }
86   -
87   - /**
88   - * Returns whether this database support initially deferrable columns.
89   - */
90   - @Override
91   - public boolean supportsInitiallyDeferrableColumns() {
92   - return true;
93   - }
94   -
95   - @Override
96   - public boolean supportsTablespaces() {
97   - return true;
98   - }
99   -
100   - @Override
101   - public int getPriority() {
102   - return PRIORITY_DEFAULT;
103   - }
104   -
105   - private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
106   -
107   - protected final int SHORT_IDENTIFIERS_LENGTH = 30;
108   - protected final int LONG_IDENTIFIERS_LEGNTH = 128;
109   - public static final int ORACLE_12C_MAJOR_VERSION = 12;
110   -
111   - private Set<String> reservedWords = new HashSet<>();
112   - private Set<String> userDefinedTypes;
113   - private Map<String, String> savedSessionNlsSettings;
114   -
115   - private Boolean canAccessDbaRecycleBin;
116   - private Integer databaseMajorVersion;
117   - private Integer databaseMinorVersion;
118   -
119   - /**
120   - * Default constructor for an object that represents the Oracle Database DBMS.
121   - */
122   - public DmDatabase() {
123   - super.unquotedObjectsAreUppercased = true;
124   - //noinspection HardCodedStringLiteral
125   - super.setCurrentDateTimeFunction("SYSTIMESTAMP");
126   - // Setting list of Oracle's native functions
127   - //noinspection HardCodedStringLiteral
128   - dateFunctions.add(new DatabaseFunction("SYSDATE"));
129   - //noinspection HardCodedStringLiteral
130   - dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
131   - //noinspection HardCodedStringLiteral
132   - dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
133   - //noinspection HardCodedStringLiteral
134   - super.sequenceNextValueFunction = "%s.nextval";
135   - //noinspection HardCodedStringLiteral
136   - super.sequenceCurrentValueFunction = "%s.currval";
137   - }
138   -
139   - private void tryProxySession(final String url, final Connection con) {
140   - Matcher m = PROXY_USER.matcher(url);
141   - if (m.matches()) {
142   - Properties props = new Properties();
143   - props.put("PROXY_USER_NAME", m.group(1));
144   - try {
145   - Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
146   - method.setAccessible(true);
147   - method.invoke(con, 1, props);
148   - } catch (Exception e) {
149   - Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
150   - }
151   - }
152   - }
153   -
154   - @Override
155   - public int getDatabaseMajorVersion() throws DatabaseException {
156   - if (databaseMajorVersion == null) {
157   - return super.getDatabaseMajorVersion();
158   - } else {
159   - return databaseMajorVersion;
160   - }
161   - }
162   -
163   - @Override
164   - public int getDatabaseMinorVersion() throws DatabaseException {
165   - if (databaseMinorVersion == null) {
166   - return super.getDatabaseMinorVersion();
167   - } else {
168   - return databaseMinorVersion;
169   - }
170   - }
171   -
172   - @Override
173   - public String getJdbcCatalogName(CatalogAndSchema schema) {
174   - return null;
175   - }
176   -
177   - @Override
178   - public String getJdbcSchemaName(CatalogAndSchema schema) {
179   - return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
180   - }
181   -
182   - @Override
183   - protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
184   - if (StringUtil.isEmpty(generationType)) {
185   - return super.getAutoIncrementClause();
186   - }
187   -
188   - String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
189   - String generationStrategy = generationType;
190   - if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
191   - generationStrategy += " ON NULL";
192   - }
193   - return String.format(autoIncrementClause, generationStrategy);
194   - }
195   -
196   - @Override
197   - public String generatePrimaryKeyName(String tableName) {
198   - if (tableName.length() > 27) {
199   - //noinspection HardCodedStringLiteral
200   - return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
201   - } else {
202   - //noinspection HardCodedStringLiteral
203   - return "PK_" + tableName.toUpperCase(Locale.US);
204   - }
205   - }
206   -
207   - @Override
208   - public boolean isReservedWord(String objectName) {
209   - return reservedWords.contains(objectName.toUpperCase());
210   - }
211   -
212   - @Override
213   - public boolean supportsSequences() {
214   - return true;
215   - }
216   -
217   - /**
218   - * Oracle supports catalogs in liquibase terms
219   - *
220   - * @return false
221   - */
222   - @Override
223   - public boolean supportsSchemas() {
224   - return false;
225   - }
226   -
227   - @Override
228   - protected String getConnectionCatalogName() throws DatabaseException {
229   - if (getConnection() instanceof OfflineConnection) {
230   - return getConnection().getCatalog();
231   - }
232   - try {
233   - //noinspection HardCodedStringLiteral
234   - return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
235   - } catch (Exception e) {
236   - //noinspection HardCodedStringLiteral
237   - Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
238   - }
239   - return null;
240   - }
241   -
242   - @Override
243   - public String getDefaultCatalogName() {//NOPMD
244   - return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
245   - }
246   -
247   - /**
248   - * <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
249   - *
250   - * <p>Convert an ISO8601 date string to one of the following results:
251   - * to_date('1995-05-23', 'YYYY-MM-DD')
252   - * to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
253   - * <p>
254   - * Implementation restriction:<br>
255   - * Currently, only the following subsets of ISO8601 are supported:<br>
256   - * <ul>
257   - * <li>YYYY-MM-DD</li>
258   - * <li>YYYY-MM-DDThh:mm:ss</li>
259   - * </ul>
260   - */
261   - @Override
262   - public String getDateLiteral(String isoDate) {
263   - String normalLiteral = super.getDateLiteral(isoDate);
264   -
265   - if (isDateOnly(isoDate)) {
266   - return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
267   - } else if (isTimeOnly(isoDate)) {
268   - return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
269   - } else if (isTimestamp(isoDate)) {
270   - return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
271   - } else if (isDateTime(isoDate)) {
272   - int seppos = normalLiteral.lastIndexOf('.');
273   - if (seppos != -1) {
274   - normalLiteral = normalLiteral.substring(0, seppos) + "'";
275   - }
276   - return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
277   - }
278   - return "UNSUPPORTED:" + isoDate;
279   - }
280   -
281   - @Override
282   - public boolean isSystemObject(DatabaseObject example) {
283   - if (example == null) {
284   - return false;
285   - }
286   -
287   - if (this.isLiquibaseObject(example)) {
288   - return false;
289   - }
290   -
291   - if (example instanceof Schema) {
292   - //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
293   - if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
294   - return true;
295   - }
296   - //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
297   - if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
298   - return true;
299   - }
300   - } else if (isSystemObject(example.getSchema())) {
301   - return true;
302   - }
303   - if (example instanceof Catalog) {
304   - //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
305   - if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
306   - return true;
307   - }
308   - } else if (example.getName() != null) {
309   - //noinspection HardCodedStringLiteral
310   - if (example.getName().startsWith("BIN$")) { //oracle deleted table
311   - boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
312   - if (!filteredInOriginalQuery) {
313   - filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
314   - }
315   -
316   - if (filteredInOriginalQuery) {
317   - return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
318   - liquibase.statement.UniqueConstraint));
319   - } else {
320   - return true;
321   - }
322   - } else //noinspection HardCodedStringLiteral
323   - if (example.getName().startsWith("AQ$")) { //oracle AQ tables
324   - return true;
325   - } else //noinspection HardCodedStringLiteral
326   - if (example.getName().startsWith("DR$")) { //oracle index tables
327   - return true;
328   - } else //noinspection HardCodedStringLiteral
329   - if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
330   - return true;
331   - } else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
332   - if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
333   - // CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
334   - return true;
335   - } else //noinspection HardCodedStringLiteral
336   - if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
337   - return true;
338   - } else //noinspection HardCodedStringLiteral
339   - if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
340   - return true;
341   - } else //noinspection HardCodedStringLiteral
342   - if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
343   - return true;
344   - } else //noinspection HardCodedStringLiteral
345   - if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
346   - return true;
347   - } else //noinspection HardCodedStringLiteral
348   - if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
349   - return true;
350   - } else //noinspection HardCodedStringLiteral
351   - if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
352   - return true;
353   - } else //noinspection HardCodedStringLiteral
354   - if (example.getName().startsWith("USLOG$")) { //for update materialized view
355   - return true;
356   - } else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
357   - return true;
358   - }
359   - }
360   -
361   - return super.isSystemObject(example);
362   - }
363   -
364   - @Override
365   - public boolean supportsAutoIncrement() {
366   - // Oracle supports Identity beginning with version 12c
367   - boolean isAutoIncrementSupported = false;
368   -
369   - try {
370   - if (getDatabaseMajorVersion() >= 12) {
371   - isAutoIncrementSupported = true;
372   - }
373   -
374   - // Returning true will generate create table command with 'IDENTITY' clause, example:
375   - // CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
376   -
377   - // While returning false will continue to generate create table command without 'IDENTITY' clause, example:
378   - // CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
379   -
380   - } catch (DatabaseException ex) {
381   - isAutoIncrementSupported = false;
382   - }
383   -
384   - return isAutoIncrementSupported;
385   - }
386   -
387   -
388   -// public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
389   -// Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
390   -//
391   -// List<Map> maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME"));
392   -//
393   -// UniqueConstraint constraint = null;
394   -// for (Map map : maps) {
395   -// if (constraint == null || !constraint.getName().equals(constraint.getName())) {
396   -// returnSet.add(constraint);
397   -// Table table = new Table((String) map.get("TABLE_NAME"));
398   -// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
399   -// }
400   -// }
401   -// if (constraint != null) {
402   -// returnSet.add(constraint);
403   -// }
404   -//
405   -// return returnSet;
406   -// }
407   -
408   - @Override
409   - public boolean supportsRestrictForeignKeys() {
410   - return false;
411   - }
412   -
413   - @Override
414   - public int getDataTypeMaxParameters(String dataTypeName) {
415   - //noinspection HardCodedStringLiteral
416   - if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
417   - return 0;
418   - }
419   - //noinspection HardCodedStringLiteral
420   - if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
421   - return 0;
422   - }
423   - return super.getDataTypeMaxParameters(dataTypeName);
424   - }
425   -
426   - public String getSystemTableWhereClause(String tableNameColumn) {
427   - List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
428   - "AQ$",
429   - "DR$",
430   - "SYS_IOT_OVER",
431   - "MLOG$_",
432   - "RUPD$_",
433   - "WM$_",
434   - "ISEQ$$_",
435   - "USLOG$",
436   - "SYS_FBA"));
437   -
438   - for (int i = 0;i<clauses.size(); i++) {
439   - clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
440   - }
441   - return "("+ StringUtil.join(clauses, " AND ") + ")";
442   - }
443   -
444   - @Override
445   - public boolean jdbcCallsCatalogsSchemas() {
446   - return true;
447   - }
448   -
449   - public Set<String> getUserDefinedTypes() {
450   - if (userDefinedTypes == null) {
451   - userDefinedTypes = new HashSet<>();
452   - if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
453   - try {
454   - try {
455   - //noinspection HardCodedStringLiteral
456   - userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
457   - } catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
458   - //noinspection HardCodedStringLiteral
459   - userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
460   - }
461   - } catch (DatabaseException e) {
462   - //ignore error
463   - }
464   - }
465   - }
466   -
467   - return userDefinedTypes;
468   - }
469   -
470   - @Override
471   - public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
472   - //noinspection HardCodedStringLiteral
473   - if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
474   - return databaseFunction.toString();
475   - }
476   - if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
477   - SequenceCurrentValueFunction)) {
478   - String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
479   - // replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
480   - return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
481   -
482   - }
483   -
484   - return super.generateDatabaseFunctionValue(databaseFunction);
485   - }
486   -
487   - @Override
488   - public ValidationErrors validate() {
489   - ValidationErrors errors = super.validate();
490   - DatabaseConnection connection = getConnection();
491   - if ((connection == null) || (connection instanceof OfflineConnection)) {
492   - //noinspection HardCodedStringLiteral
493   - Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
494   - return errors;
495   - }
496   -
497   - if (!canAccessDbaRecycleBin()) {
498   - errors.addWarning(getDbaRecycleBinWarning());
499   - }
500   -
501   - return errors;
502   -
503   - }
504   -
505   - public String getDbaRecycleBinWarning() {
506   - //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
507   - // HardCodedStringLiteral
508   - //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
509   - return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
510   - "constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
511   - "referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
512   - " issue.\n" +
513   - "\n" +
514   - "The user you used to connect to the database (" + getConnection().getConnectionUserName() +
515   - ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
516   - "Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
517   - "\n" +
518   - " GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
519   - }
520   -
521   - public boolean canAccessDbaRecycleBin() {
522   - if (canAccessDbaRecycleBin == null) {
523   - DatabaseConnection connection = getConnection();
524   - if ((connection == null) || (connection instanceof OfflineConnection)) {
525   - return false;
526   - }
527   -
528   - Statement statement = null;
529   - try {
530   - statement = ((JdbcConnection) connection).createStatement();
531   - @SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
532   - resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
533   - this.canAccessDbaRecycleBin = true;
534   - } catch (Exception e) {
535   - //noinspection HardCodedStringLiteral
536   - if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
537   - this.canAccessDbaRecycleBin = false;
538   - } else {
539   - //noinspection HardCodedStringLiteral
540   - Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
541   - this.canAccessDbaRecycleBin = false;
542   - }
543   - } finally {
544   - JdbcUtils.close(null, statement);
545   - }
546   - }
547   -
548   - return canAccessDbaRecycleBin;
549   - }
550   -
551   - @Override
552   - public boolean supportsNotNullConstraintNames() {
553   - return true;
554   - }
555   -
556   - /**
557   - * Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
558   - * the following form (case-insensitive comparison):
559   - * 1st character: A-Z
560   - * 2..n characters: A-Z0-9$_#
561   - * The maximum length of an identifier differs by Oracle version and object type.
562   - */
563   - public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
564   - if ((identifier == null) || (identifier.length() < 1))
565   - return false;
566   -
567   - if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
568   - return false;
569   -
570   - /*
571   - * @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
572   - * we do know seem to be supported as 12cR2 long identifiers, so:
573   - */
574   - return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
575   - }
576   -
577   - /**
578   - * Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
579   - * is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
580   - * object types).
581   - *
582   - * @return the maximum length of an object identifier, in bytes
583   - */
584   - public int getIdentifierMaximumLength() {
585   - try {
586   - if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
587   - return SHORT_IDENTIFIERS_LENGTH;
588   - } else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
589   - return SHORT_IDENTIFIERS_LENGTH;
590   - } else {
591   - return LONG_IDENTIFIERS_LEGNTH;
592   - }
593   - } catch (DatabaseException ex) {
594   - throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
595   - }
596   -
597   - }
598   -}
sql/dm/flowable-patch/src/main/java/liquibase/datatype/core/BooleanType.java deleted
1   -package liquibase.datatype.core;
2   -
3   -import liquibase.change.core.LoadDataChange;
4   -import liquibase.database.Database;
5   -import liquibase.database.core.*;
6   -import liquibase.datatype.DataTypeInfo;
7   -import liquibase.datatype.DatabaseDataType;
8   -import liquibase.datatype.LiquibaseDataType;
9   -import liquibase.exception.UnexpectedLiquibaseException;
10   -import liquibase.statement.DatabaseFunction;
11   -import liquibase.util.StringUtil;
12   -
13   -import java.util.Locale;
14   -import java.util.regex.Pattern;
15   -
16   -@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
17   -public class BooleanType extends LiquibaseDataType {
18   -
19   - @Override
20   - public DatabaseDataType toDatabaseDataType(Database database) {
21   - String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
22   - if ((database instanceof Firebird3Database)) {
23   - return new DatabaseDataType("BOOLEAN");
24   - }
25   -
26   - if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
27   - return new DatabaseDataType("SMALLINT");
28   - } else if (database instanceof MSSQLDatabase) {
29   - return new DatabaseDataType(database.escapeDataTypeName("bit"));
30   - } else if (database instanceof MySQLDatabase) {
31   - if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
32   - return new DatabaseDataType("BIT", getParameters());
33   - }
34   - return new DatabaseDataType("BIT", 1);
35   - } else if (database instanceof OracleDatabase) {
36   - return new DatabaseDataType("NUMBER", 1);
37   - } else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
38   - return new DatabaseDataType("BIT");
39   - } else if (database instanceof DerbyDatabase) {
40   - if (((DerbyDatabase) database).supportsBooleanDataType()) {
41   - return new DatabaseDataType("BOOLEAN");
42   - } else {
43   - return new DatabaseDataType("SMALLINT");
44   - }
45   - } else if (database instanceof DB2Database) {
46   - if (((DB2Database) database).supportsBooleanDataType())
47   - return new DatabaseDataType("BOOLEAN");
48   - else
49   - return new DatabaseDataType("SMALLINT");
50   - } else if (database instanceof HsqlDatabase) {
51   - return new DatabaseDataType("BOOLEAN");
52   - } else if (database instanceof PostgresDatabase) {
53   - if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
54   - return new DatabaseDataType("BIT", getParameters());
55   - }
56   - } else if (database instanceof DmDatabase) { // dhb52: DM Support
57   - return new DatabaseDataType("bit");
58   - }
59   -
60   - return super.toDatabaseDataType(database);
61   - }
62   -
63   - @Override
64   - public String objectToSql(Object value, Database database) {
65   - if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
66   - return null;
67   - }
68   -
69   - String returnValue;
70   - if (value instanceof String) {
71   - value = ((String) value).replaceAll("'", "");
72   - if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
73   - returnValue = this.getTrueBooleanValue(database);
74   - } else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
75   - ((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
76   - returnValue = this.getFalseBooleanValue(database);
77   - } else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
78   - returnValue = "b'"
79   - + value.toString()
80   - .replace("b", "")
81   - .replace("\"", "")
82   - .replace("::it", "")
83   - + "'::\"bit\"";
84   - } else {
85   - throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
86   - }
87   - } else if (value instanceof Long) {
88   - if (Long.valueOf(1).equals(value)) {
89   - returnValue = this.getTrueBooleanValue(database);
90   - } else {
91   - returnValue = this.getFalseBooleanValue(database);
92   - }
93   - } else if (value instanceof Number) {
94   - if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
95   - returnValue = this.getTrueBooleanValue(database);
96   - } else {
97   - returnValue = this.getFalseBooleanValue(database);
98   - }
99   - } else if (value instanceof DatabaseFunction) {
100   - return value.toString();
101   - } else if (value instanceof Boolean) {
102   - if (((Boolean) value)) {
103   - returnValue = this.getTrueBooleanValue(database);
104   - } else {
105   - returnValue = this.getFalseBooleanValue(database);
106   - }
107   - } else {
108   - throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
109   - }
110   -
111   - return returnValue;
112   - }
113   -
114   - protected boolean isNumericBoolean(Database database) {
115   - if (database instanceof Firebird3Database) {
116   - return false;
117   - }
118   - if (database instanceof DerbyDatabase) {
119   - return !((DerbyDatabase) database).supportsBooleanDataType();
120   - } else if (database instanceof DB2Database) {
121   - return !((DB2Database) database).supportsBooleanDataType();
122   - }
123   - return (database instanceof Db2zDatabase)
124   - || (database instanceof FirebirdDatabase)
125   - || (database instanceof MSSQLDatabase)
126   - || (database instanceof MySQLDatabase)
127   - || (database instanceof OracleDatabase)
128   - || (database instanceof SQLiteDatabase)
129   - || (database instanceof SybaseASADatabase)
130   - || (database instanceof SybaseDatabase)
131   - || (database instanceof DmDatabase); // dhb52: DM Support
132   - }
133   -
134   - /**
135   - * The database-specific value to use for "false" "boolean" columns.
136   - */
137   - public String getFalseBooleanValue(Database database) {
138   - if (isNumericBoolean(database)) {
139   - return "0";
140   - }
141   - if (database instanceof InformixDatabase) {
142   - return "'f'";
143   - }
144   - return "FALSE";
145   - }
146   -
147   - /**
148   - * The database-specific value to use for "true" "boolean" columns.
149   - */
150   - public String getTrueBooleanValue(Database database) {
151   - if (isNumericBoolean(database)) {
152   - return "1";
153   - }
154   - if (database instanceof InformixDatabase) {
155   - return "'t'";
156   - }
157   - return "TRUE";
158   - }
159   -
160   - @Override
161   - public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
162   - return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
163   - }
164   -
165   -}
sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java deleted
1   -/* Licensed under the Apache License, Version 2.0 (the "License");
2   - * you may not use this file except in compliance with the License.
3   - * You may obtain a copy of the License at
4   - *
5   - * http://www.apache.org/licenses/LICENSE-2.0
6   - *
7   - * Unless required by applicable law or agreed to in writing, software
8   - * distributed under the License is distributed on an "AS IS" BASIS,
9   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10   - * See the License for the specific language governing permissions and
11   - * limitations under the License.
12   - */
13   -package org.flowable.common.engine.impl;
14   -
15   -import java.io.InputStream;
16   -import java.io.InputStreamReader;
17   -import java.io.Reader;
18   -import java.sql.Connection;
19   -import java.sql.DatabaseMetaData;
20   -import java.sql.PreparedStatement;
21   -import java.sql.ResultSet;
22   -import java.sql.SQLException;
23   -import java.time.Duration;
24   -import java.util.ArrayList;
25   -import java.util.Collection;
26   -import java.util.Collections;
27   -import java.util.Comparator;
28   -import java.util.HashMap;
29   -import java.util.List;
30   -import java.util.Map;
31   -import java.util.Properties;
32   -import java.util.ServiceLoader;
33   -import java.util.Set;
34   -
35   -import javax.naming.InitialContext;
36   -import javax.sql.DataSource;
37   -
38   -import org.apache.commons.lang3.StringUtils;
39   -import org.apache.ibatis.builder.xml.XMLConfigBuilder;
40   -import org.apache.ibatis.builder.xml.XMLMapperBuilder;
41   -import org.apache.ibatis.datasource.pooled.PooledDataSource;
42   -import org.apache.ibatis.mapping.Environment;
43   -import org.apache.ibatis.plugin.Interceptor;
44   -import org.apache.ibatis.session.Configuration;
45   -import org.apache.ibatis.session.SqlSessionFactory;
46   -import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
47   -import org.apache.ibatis.transaction.TransactionFactory;
48   -import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
49   -import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
50   -import org.apache.ibatis.type.ArrayTypeHandler;
51   -import org.apache.ibatis.type.BigDecimalTypeHandler;
52   -import org.apache.ibatis.type.BlobInputStreamTypeHandler;
53   -import org.apache.ibatis.type.BlobTypeHandler;
54   -import org.apache.ibatis.type.BooleanTypeHandler;
55   -import org.apache.ibatis.type.ByteTypeHandler;
56   -import org.apache.ibatis.type.ClobTypeHandler;
57   -import org.apache.ibatis.type.DateOnlyTypeHandler;
58   -import org.apache.ibatis.type.DateTypeHandler;
59   -import org.apache.ibatis.type.DoubleTypeHandler;
60   -import org.apache.ibatis.type.FloatTypeHandler;
61   -import org.apache.ibatis.type.IntegerTypeHandler;
62   -import org.apache.ibatis.type.JdbcType;
63   -import org.apache.ibatis.type.LongTypeHandler;
64   -import org.apache.ibatis.type.NClobTypeHandler;
65   -import org.apache.ibatis.type.NStringTypeHandler;
66   -import org.apache.ibatis.type.ShortTypeHandler;
67   -import org.apache.ibatis.type.SqlxmlTypeHandler;
68   -import org.apache.ibatis.type.StringTypeHandler;
69   -import org.apache.ibatis.type.TimeOnlyTypeHandler;
70   -import org.apache.ibatis.type.TypeHandlerRegistry;
71   -import org.flowable.common.engine.api.FlowableException;
72   -import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
73   -import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
74   -import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
75   -import org.flowable.common.engine.api.engine.EngineLifecycleListener;
76   -import org.flowable.common.engine.impl.agenda.AgendaOperationRunner;
77   -import org.flowable.common.engine.impl.cfg.CommandExecutorImpl;
78   -import org.flowable.common.engine.impl.cfg.IdGenerator;
79   -import org.flowable.common.engine.impl.cfg.TransactionContextFactory;
80   -import org.flowable.common.engine.impl.cfg.standalone.StandaloneMybatisTransactionContextFactory;
81   -import org.flowable.common.engine.impl.db.CommonDbSchemaManager;
82   -import org.flowable.common.engine.impl.db.DbSqlSessionFactory;
83   -import org.flowable.common.engine.impl.db.LogSqlExecutionTimePlugin;
84   -import org.flowable.common.engine.impl.db.MybatisTypeAliasConfigurator;
85   -import org.flowable.common.engine.impl.db.MybatisTypeHandlerConfigurator;
86   -import org.flowable.common.engine.impl.db.SchemaManager;
87   -import org.flowable.common.engine.impl.event.EventDispatchAction;
88   -import org.flowable.common.engine.impl.event.FlowableEventDispatcherImpl;
89   -import org.flowable.common.engine.impl.interceptor.Command;
90   -import org.flowable.common.engine.impl.interceptor.CommandConfig;
91   -import org.flowable.common.engine.impl.interceptor.CommandContextFactory;
92   -import org.flowable.common.engine.impl.interceptor.CommandContextInterceptor;
93   -import org.flowable.common.engine.impl.interceptor.CommandExecutor;
94   -import org.flowable.common.engine.impl.interceptor.CommandInterceptor;
95   -import org.flowable.common.engine.impl.interceptor.CrDbRetryInterceptor;
96   -import org.flowable.common.engine.impl.interceptor.DefaultCommandInvoker;
97   -import org.flowable.common.engine.impl.interceptor.LogInterceptor;
98   -import org.flowable.common.engine.impl.interceptor.SessionFactory;
99   -import org.flowable.common.engine.impl.interceptor.TransactionContextInterceptor;
100   -import org.flowable.common.engine.impl.lock.LockManager;
101   -import org.flowable.common.engine.impl.lock.LockManagerImpl;
102   -import org.flowable.common.engine.impl.logging.LoggingListener;
103   -import org.flowable.common.engine.impl.logging.LoggingSession;
104   -import org.flowable.common.engine.impl.logging.LoggingSessionFactory;
105   -import org.flowable.common.engine.impl.persistence.GenericManagerFactory;
106   -import org.flowable.common.engine.impl.persistence.StrongUuidGenerator;
107   -import org.flowable.common.engine.impl.persistence.cache.EntityCache;
108   -import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl;
109   -import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManager;
110   -import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManagerImpl;
111   -import org.flowable.common.engine.impl.persistence.entity.Entity;
112   -import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManager;
113   -import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManagerImpl;
114   -import org.flowable.common.engine.impl.persistence.entity.TableDataManager;
115   -import org.flowable.common.engine.impl.persistence.entity.TableDataManagerImpl;
116   -import org.flowable.common.engine.impl.persistence.entity.data.ByteArrayDataManager;
117   -import org.flowable.common.engine.impl.persistence.entity.data.PropertyDataManager;
118   -import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisByteArrayDataManager;
119   -import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisPropertyDataManager;
120   -import org.flowable.common.engine.impl.runtime.Clock;
121   -import org.flowable.common.engine.impl.service.CommonEngineServiceImpl;
122   -import org.flowable.common.engine.impl.util.DefaultClockImpl;
123   -import org.flowable.common.engine.impl.util.IoUtil;
124   -import org.flowable.common.engine.impl.util.ReflectUtil;
125   -import org.flowable.eventregistry.api.EventRegistryEventConsumer;
126   -import org.slf4j.Logger;
127   -import org.slf4j.LoggerFactory;
128   -
129   -import com.fasterxml.jackson.databind.ObjectMapper;
130   -import com.fasterxml.jackson.databind.SerializationFeature;
131   -
132   -public abstract class AbstractEngineConfiguration {
133   -
134   - protected final Logger logger = LoggerFactory.getLogger(getClass());
135   -
136   - /** The tenant id indicating 'no tenant' */
137   - public static final String NO_TENANT_ID = "";
138   -
139   - /**
140   - * Checks the version of the DB schema against the library when the form engine is being created and throws an exception if the versions don't match.
141   - */
142   - public static final String DB_SCHEMA_UPDATE_FALSE = "false";
143   - public static final String DB_SCHEMA_UPDATE_CREATE = "create";
144   - public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop";
145   -
146   - /**
147   - * Creates the schema when the form engine is being created and drops the schema when the form engine is being closed.
148   - */
149   - public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create";
150   -
151   - /**
152   - * Upon building of the process engine, a check is performed and an update of the schema is performed if it is necessary.
153   - */
154   - public static final String DB_SCHEMA_UPDATE_TRUE = "true";
155   -
156   - protected boolean forceCloseMybatisConnectionPool = true;
157   -
158   - protected String databaseType;
159   - protected String jdbcDriver = "org.h2.Driver";
160   - protected String jdbcUrl = "jdbc:h2:tcp://localhost/~/flowable";
161   - protected String jdbcUsername = "sa";
162   - protected String jdbcPassword = "";
163   - protected String dataSourceJndiName;
164   - protected int jdbcMaxActiveConnections = 16;
165   - protected int jdbcMaxIdleConnections = 8;
166   - protected int jdbcMaxCheckoutTime;
167   - protected int jdbcMaxWaitTime;
168   - protected boolean jdbcPingEnabled;
169   - protected String jdbcPingQuery;
170   - protected int jdbcPingConnectionNotUsedFor;
171   - protected int jdbcDefaultTransactionIsolationLevel;
172   - protected DataSource dataSource;
173   - protected SchemaManager commonSchemaManager;
174   - protected SchemaManager schemaManager;
175   - protected Command<Void> schemaManagementCmd;
176   -
177   - protected String databaseSchemaUpdate = DB_SCHEMA_UPDATE_FALSE;
178   -
179   - /**
180   - * Whether to use a lock when performing the database schema create or update operations.
181   - */
182   - protected boolean useLockForDatabaseSchemaUpdate = false;
183   -
184   - protected String xmlEncoding = "UTF-8";
185   -
186   - // COMMAND EXECUTORS ///////////////////////////////////////////////
187   -
188   - protected CommandExecutor commandExecutor;
189   - protected Collection<? extends CommandInterceptor> defaultCommandInterceptors;
190   - protected CommandConfig defaultCommandConfig;
191   - protected CommandConfig schemaCommandConfig;
192   - protected CommandContextFactory commandContextFactory;
193   - protected CommandInterceptor commandInvoker;
194   -
195   - protected AgendaOperationRunner agendaOperationRunner = (commandContext, runnable) -> runnable.run();
196   -
197   - protected List<CommandInterceptor> customPreCommandInterceptors;
198   - protected List<CommandInterceptor> customPostCommandInterceptors;
199   - protected List<CommandInterceptor> commandInterceptors;
200   -
201   - protected Map<String, AbstractEngineConfiguration> engineConfigurations = new HashMap<>();
202   - protected Map<String, AbstractServiceConfiguration> serviceConfigurations = new HashMap<>();
203   -
204   - protected ClassLoader classLoader;
205   - /**
206   - * Either use Class.forName or ClassLoader.loadClass for class loading. See http://forums.activiti.org/content/reflectutilloadclass-and-custom- classloader
207   - */
208   - protected boolean useClassForNameClassLoading = true;
209   -
210   - protected List<EngineLifecycleListener> engineLifecycleListeners;
211   -
212   - // Event Registry //////////////////////////////////////////////////
213   - protected Map<String, EventRegistryEventConsumer> eventRegistryEventConsumers = new HashMap<>();
214   -
215   - // MYBATIS SQL SESSION FACTORY /////////////////////////////////////
216   -
217   - protected boolean isDbHistoryUsed = true;
218   - protected DbSqlSessionFactory dbSqlSessionFactory;
219   - protected SqlSessionFactory sqlSessionFactory;
220   - protected TransactionFactory transactionFactory;
221   - protected TransactionContextFactory transactionContextFactory;
222   -
223   - /**
224   - * If set to true, enables bulk insert (grouping sql inserts together). Default true.
225   - * For some databases (eg DB2+z/OS) needs to be set to false.
226   - */
227   - protected boolean isBulkInsertEnabled = true;
228   -
229   - /**
230   - * Some databases have a limit of how many parameters one sql insert can have (eg SQL Server, 2000 params (!= insert statements) ). Tweak this parameter in case of exceptions indicating too much
231   - * is being put into one bulk insert, or make it higher if your database can cope with it and there are inserts with a huge amount of data.
232   - * <p>
233   - * By default: 100 (55 for mssql server as it has a hard limit of 2000 parameters in a statement)
234   - */
235   - protected int maxNrOfStatementsInBulkInsert = 100;
236   -
237   - public int DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER = 55; // currently Execution has most params (35). 2000 / 35 = 57.
238   -
239   - protected String mybatisMappingFile;
240   - protected Set<Class<?>> customMybatisMappers;
241   - protected Set<String> customMybatisXMLMappers;
242   - protected List<Interceptor> customMybatisInterceptors;
243   -
244   - protected Set<String> dependentEngineMyBatisXmlMappers;
245   - protected List<MybatisTypeAliasConfigurator> dependentEngineMybatisTypeAliasConfigs;
246   - protected List<MybatisTypeHandlerConfigurator> dependentEngineMybatisTypeHandlerConfigs;
247   -
248   - // SESSION FACTORIES ///////////////////////////////////////////////
249   - protected List<SessionFactory> customSessionFactories;
250   - protected Map<Class<?>, SessionFactory> sessionFactories;
251   -
252   - protected boolean enableEventDispatcher = true;
253   - protected FlowableEventDispatcher eventDispatcher;
254   - protected List<FlowableEventListener> eventListeners;
255   - protected Map<String, List<FlowableEventListener>> typedEventListeners;
256   - protected List<EventDispatchAction> additionalEventDispatchActions;
257   -
258   - protected LoggingListener loggingListener;
259   -
260   - protected boolean transactionsExternallyManaged;
261   -
262   - /**
263   - * Flag that can be set to configure or not a relational database is used. This is useful for custom implementations that do not use relational databases at all.
264   - *
265   - * If true (default), the {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will be used to determine what needs to happen wrt the database schema.
266   - *
267   - * If false, no validation or schema creation will be done. That means that the database schema must have been created 'manually' before but the engine does not validate whether the schema is
268   - * correct. The {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will not be used.
269   - */
270   - protected boolean usingRelationalDatabase = true;
271   -
272   - /**
273   - * Flag that can be set to configure whether or not a schema is used. This is useful for custom implementations that do not use relational databases at all.
274   - * Setting {@link #usingRelationalDatabase} to true will automatically imply using a schema.
275   - */
276   - protected boolean usingSchemaMgmt = true;
277   -
278   - /**
279   - * Allows configuring a database table prefix which is used for all runtime operations of the process engine. For example, if you specify a prefix named 'PRE1.', Flowable will query for executions
280   - * in a table named 'PRE1.ACT_RU_EXECUTION_'.
281   - *
282   - * <p>
283   - * <strong>NOTE: the prefix is not respected by automatic database schema management. If you use {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_CREATE_DROP} or
284   - * {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_TRUE}, Flowable will create the database tables using the default names, regardless of the prefix configured here.</strong>
285   - */
286   - protected String databaseTablePrefix = "";
287   -
288   - /**
289   - * Escape character for doing wildcard searches.
290   - *
291   - * This will be added at then end of queries that include for example a LIKE clause. For example: SELECT * FROM table WHERE column LIKE '%\%%' ESCAPE '\';
292   - */
293   - protected String databaseWildcardEscapeCharacter;
294   -
295   - /**
296   - * database catalog to use
297   - */
298   - protected String databaseCatalog = "";
299   -
300   - /**
301   - * In some situations you want to set the schema to use for table checks / generation if the database metadata doesn't return that correctly, see https://jira.codehaus.org/browse/ACT-1220,
302   - * https://jira.codehaus.org/browse/ACT-1062
303   - */
304   - protected String databaseSchema;
305   -
306   - /**
307   - * Set to true in case the defined databaseTablePrefix is a schema-name, instead of an actual table name prefix. This is relevant for checking if Flowable-tables exist, the databaseTablePrefix
308   - * will not be used here - since the schema is taken into account already, adding a prefix for the table-check will result in wrong table-names.
309   - */
310   - protected boolean tablePrefixIsSchema;
311   -
312   - /**
313   - * Set to true if the latest version of a definition should be retrieved, ignoring a possible parent deployment id value
314   - */
315   - protected boolean alwaysLookupLatestDefinitionVersion;
316   -
317   - /**
318   - * Set to true if by default lookups should fallback to the default tenant (an empty string by default or a defined tenant value)
319   - */
320   - protected boolean fallbackToDefaultTenant;
321   -
322   - /**
323   - * Default tenant provider that is executed when looking up definitions, in case the global or local fallback to default tenant value is true
324   - */
325   - protected DefaultTenantProvider defaultTenantProvider = (tenantId, scope, scopeKey) -> NO_TENANT_ID;
326   -
327   - /**
328   - * Enables the MyBatis plugin that logs the execution time of sql statements.
329   - */
330   - protected boolean enableLogSqlExecutionTime;
331   -
332   - protected Properties databaseTypeMappings = getDefaultDatabaseTypeMappings();
333   -
334   - /**
335   - * Duration between the checks when acquiring a lock.
336   - */
337   - protected Duration lockPollRate = Duration.ofSeconds(10);
338   -
339   - /**
340   - * Duration to wait for the DB Schema lock before giving up.
341   - */
342   - protected Duration schemaLockWaitTime = Duration.ofMinutes(5);
343   -
344   - // DATA MANAGERS //////////////////////////////////////////////////////////////////
345   -
346   - protected PropertyDataManager propertyDataManager;
347   - protected ByteArrayDataManager byteArrayDataManager;
348   - protected TableDataManager tableDataManager;
349   -
350   - // ENTITY MANAGERS ////////////////////////////////////////////////////////////////
351   -
352   - protected PropertyEntityManager propertyEntityManager;
353   - protected ByteArrayEntityManager byteArrayEntityManager;
354   -
355   - protected List<EngineDeployer> customPreDeployers;
356   - protected List<EngineDeployer> customPostDeployers;
357   - protected List<EngineDeployer> deployers;
358   -
359   - // CONFIGURATORS ////////////////////////////////////////////////////////////
360   -
361   - protected boolean enableConfiguratorServiceLoader = true; // Enabled by default. In certain environments this should be set to false (eg osgi)
362   - protected List<EngineConfigurator> configurators; // The injected configurators
363   - protected List<EngineConfigurator> allConfigurators; // Including auto-discovered configurators
364   - protected EngineConfigurator idmEngineConfigurator;
365   - protected EngineConfigurator eventRegistryConfigurator;
366   -
367   - public static final String PRODUCT_NAME_POSTGRES = "PostgreSQL";
368   - public static final String PRODUCT_NAME_CRDB = "CockroachDB";
369   -
370   - public static final String DATABASE_TYPE_H2 = "h2";
371   - public static final String DATABASE_TYPE_HSQL = "hsql";
372   - public static final String DATABASE_TYPE_MYSQL = "mysql";
373   - public static final String DATABASE_TYPE_ORACLE = "oracle";
374   - public static final String DATABASE_TYPE_POSTGRES = "postgres";
375   - public static final String DATABASE_TYPE_MSSQL = "mssql";
376   - public static final String DATABASE_TYPE_DB2 = "db2";
377   - public static final String DATABASE_TYPE_COCKROACHDB = "cockroachdb";
378   -
379   - public static Properties getDefaultDatabaseTypeMappings() {
380   - Properties databaseTypeMappings = new Properties();
381   - databaseTypeMappings.setProperty("H2", DATABASE_TYPE_H2);
382   - databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL);
383   - databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL);
384   - databaseTypeMappings.setProperty("MariaDB", DATABASE_TYPE_MYSQL);
385   - databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE);
386   - databaseTypeMappings.setProperty(PRODUCT_NAME_POSTGRES, DATABASE_TYPE_POSTGRES);
387   - databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL);
388   - databaseTypeMappings.setProperty(DATABASE_TYPE_DB2, DATABASE_TYPE_DB2);
389   - databaseTypeMappings.setProperty("DB2", DATABASE_TYPE_DB2);
390   - databaseTypeMappings.setProperty("DB2/NT", DATABASE_TYPE_DB2);
391   - databaseTypeMappings.setProperty("DB2/NT64", DATABASE_TYPE_DB2);
392   - databaseTypeMappings.setProperty("DB2 UDP", DATABASE_TYPE_DB2);
393   - databaseTypeMappings.setProperty("DB2/LINUX", DATABASE_TYPE_DB2);
394   - databaseTypeMappings.setProperty("DB2/LINUX390", DATABASE_TYPE_DB2);
395   - databaseTypeMappings.setProperty("DB2/LINUXX8664", DATABASE_TYPE_DB2);
396   - databaseTypeMappings.setProperty("DB2/LINUXZ64", DATABASE_TYPE_DB2);
397   - databaseTypeMappings.setProperty("DB2/LINUXPPC64", DATABASE_TYPE_DB2);
398   - databaseTypeMappings.setProperty("DB2/LINUXPPC64LE", DATABASE_TYPE_DB2);
399   - databaseTypeMappings.setProperty("DB2/400 SQL", DATABASE_TYPE_DB2);
400   - databaseTypeMappings.setProperty("DB2/6000", DATABASE_TYPE_DB2);
401   - databaseTypeMappings.setProperty("DB2 UDB iSeries", DATABASE_TYPE_DB2);
402   - databaseTypeMappings.setProperty("DB2/AIX64", DATABASE_TYPE_DB2);
403   - databaseTypeMappings.setProperty("DB2/HPUX", DATABASE_TYPE_DB2);
404   - databaseTypeMappings.setProperty("DB2/HP64", DATABASE_TYPE_DB2);
405   - databaseTypeMappings.setProperty("DB2/SUN", DATABASE_TYPE_DB2);
406   - databaseTypeMappings.setProperty("DB2/SUN64", DATABASE_TYPE_DB2);
407   - databaseTypeMappings.setProperty("DB2/PTX", DATABASE_TYPE_DB2);
408   - databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2);
409   - databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2);
410   - databaseTypeMappings.setProperty(PRODUCT_NAME_CRDB, DATABASE_TYPE_COCKROACHDB);
411   - databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_ORACLE); // dhb52: DM support
412   - return databaseTypeMappings;
413   - }
414   -
415   - protected Map<Object, Object> beans;
416   -
417   - protected IdGenerator idGenerator;
418   - protected boolean usePrefixId;
419   -
420   - protected Clock clock;
421   - protected ObjectMapper objectMapper;
422   -
423   - // Variables
424   -
425   - public static final int DEFAULT_GENERIC_MAX_LENGTH_STRING = 4000;
426   - public static final int DEFAULT_ORACLE_MAX_LENGTH_STRING = 2000;
427   -
428   - /**
429   - * Define a max length for storing String variable types in the database. Mainly used for the Oracle NVARCHAR2 limit of 2000 characters
430   - */
431   - protected int maxLengthStringVariableType = -1;
432   -
433   - protected void initEngineConfigurations() {
434   - addEngineConfiguration(getEngineCfgKey(), getEngineScopeType(), this);
435   - }
436   -
437   - // DataSource
438   - // ///////////////////////////////////////////////////////////////
439   -
440   - protected void initDataSource() {
441   - if (dataSource == null) {
442   - if (dataSourceJndiName != null) {
443   - try {
444   - dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName);
445   - } catch (Exception e) {
446   - throw new FlowableException("couldn't lookup datasource from " + dataSourceJndiName + ": " + e.getMessage(), e);
447   - }
448   -
449   - } else if (jdbcUrl != null) {
450   - if ((jdbcDriver == null) || (jdbcUsername == null)) {
451   - throw new FlowableException("DataSource or JDBC properties have to be specified in a process engine configuration");
452   - }
453   -
454   - logger.debug("initializing datasource to db: {}", jdbcUrl);
455   -
456   - if (logger.isInfoEnabled()) {
457   - logger.info("Configuring Datasource with following properties (omitted password for security)");
458   - logger.info("datasource driver : {}", jdbcDriver);
459   - logger.info("datasource url : {}", jdbcUrl);
460   - logger.info("datasource user name : {}", jdbcUsername);
461   - }
462   -
463   - PooledDataSource pooledDataSource = new PooledDataSource(this.getClass().getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);
464   -
465   - if (jdbcMaxActiveConnections > 0) {
466   - pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);
467   - }
468   - if (jdbcMaxIdleConnections > 0) {
469   - pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);
470   - }
471   - if (jdbcMaxCheckoutTime > 0) {
472   - pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);
473   - }
474   - if (jdbcMaxWaitTime > 0) {
475   - pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);
476   - }
477   - if (jdbcPingEnabled) {
478   - pooledDataSource.setPoolPingEnabled(true);
479   - if (jdbcPingQuery != null) {
480   - pooledDataSource.setPoolPingQuery(jdbcPingQuery);
481   - }
482   - pooledDataSource.setPoolPingConnectionsNotUsedFor(jdbcPingConnectionNotUsedFor);
483   - }
484   - if (jdbcDefaultTransactionIsolationLevel > 0) {
485   - pooledDataSource.setDefaultTransactionIsolationLevel(jdbcDefaultTransactionIsolationLevel);
486   - }
487   - dataSource = pooledDataSource;
488   - }
489   - }
490   -
491   - if (databaseType == null) {
492   - initDatabaseType();
493   - }
494   - }
495   -
496   - public void initDatabaseType() {
497   - Connection connection = null;
498   - try {
499   - connection = dataSource.getConnection();
500   - DatabaseMetaData databaseMetaData = connection.getMetaData();
501   - String databaseProductName = databaseMetaData.getDatabaseProductName();
502   - logger.debug("database product name: '{}'", databaseProductName);
503   -
504   - // CRDB does not expose the version through the jdbc driver, so we need to fetch it through version().
505   - if (PRODUCT_NAME_POSTGRES.equalsIgnoreCase(databaseProductName)) {
506   - try (PreparedStatement preparedStatement = connection.prepareStatement("select version() as version;");
507   - ResultSet resultSet = preparedStatement.executeQuery()) {
508   - String version = null;
509   - if (resultSet.next()) {
510   - version = resultSet.getString("version");
511   - }
512   -
513   - if (StringUtils.isNotEmpty(version) && version.toLowerCase().startsWith(PRODUCT_NAME_CRDB.toLowerCase())) {
514   - databaseProductName = PRODUCT_NAME_CRDB;
515   - logger.info("CockroachDB version '{}' detected", version);
516   - }
517   - }
518   - }
519   -
520   - databaseType = databaseTypeMappings.getProperty(databaseProductName);
521   - if (databaseType == null) {
522   - throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'");
523   - }
524   - logger.debug("using database type: {}", databaseType);
525   -
526   - } catch (SQLException e) {
527   - throw new RuntimeException("Exception while initializing Database connection", e);
528   - } finally {
529   - try {
530   - if (connection != null) {
531   - connection.close();
532   - }
533   - } catch (SQLException e) {
534   - logger.error("Exception while closing the Database connection", e);
535   - }
536   - }
537   -
538   - // Special care for MSSQL, as it has a hard limit of 2000 params per statement (incl bulk statement).
539   - // Especially with executions, with 100 as default, this limit is passed.
540   - if (DATABASE_TYPE_MSSQL.equals(databaseType)) {
541   - maxNrOfStatementsInBulkInsert = DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER;
542   - }
543   - }
544   -
545   - public void initSchemaManager() {
546   - if (this.commonSchemaManager == null) {
547   - this.commonSchemaManager = new CommonDbSchemaManager();
548   - }
549   - }
550   -
551   - // session factories ////////////////////////////////////////////////////////
552   -
553   - public void addSessionFactory(SessionFactory sessionFactory) {
554   - sessionFactories.put(sessionFactory.getSessionType(), sessionFactory);
555   - }
556   -
557   - public void initCommandContextFactory() {
558   - if (commandContextFactory == null) {
559   - commandContextFactory = new CommandContextFactory();
560   - }
561   - }
562   -
563   - public void initTransactionContextFactory() {
564   - if (transactionContextFactory == null) {
565   - transactionContextFactory = new StandaloneMybatisTransactionContextFactory();
566   - }
567   - }
568   -
569   - public void initCommandExecutors() {
570   - initDefaultCommandConfig();
571   - initSchemaCommandConfig();
572   - initCommandInvoker();
573   - initCommandInterceptors();
574   - initCommandExecutor();
575   - }
576   -
577   -
578   - public void initDefaultCommandConfig() {
579   - if (defaultCommandConfig == null) {
580   - defaultCommandConfig = new CommandConfig();
581   - }
582   - }
583   -
584   - public void initSchemaCommandConfig() {
585   - if (schemaCommandConfig == null) {
586   - schemaCommandConfig = new CommandConfig();
587   - }
588   - }
589   -
590   - public void initCommandInvoker() {
591   - if (commandInvoker == null) {
592   - commandInvoker = new DefaultCommandInvoker();
593   - }
594   - }
595   -
596   - public void initCommandInterceptors() {
597   - if (commandInterceptors == null) {
598   - commandInterceptors = new ArrayList<>();
599   - if (customPreCommandInterceptors != null) {
600   - commandInterceptors.addAll(customPreCommandInterceptors);
601   - }
602   - commandInterceptors.addAll(getDefaultCommandInterceptors());
603   - if (customPostCommandInterceptors != null) {
604   - commandInterceptors.addAll(customPostCommandInterceptors);
605   - }
606   - commandInterceptors.add(commandInvoker);
607   - }
608   - }
609   -
610   - public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
611   - if (defaultCommandInterceptors == null) {
612   - List<CommandInterceptor> interceptors = new ArrayList<>();
613   - interceptors.add(new LogInterceptor());
614   -
615   - if (DATABASE_TYPE_COCKROACHDB.equals(databaseType)) {
616   - interceptors.add(new CrDbRetryInterceptor());
617   - }
618   -
619   - CommandInterceptor transactionInterceptor = createTransactionInterceptor();
620   - if (transactionInterceptor != null) {
621   - interceptors.add(transactionInterceptor);
622   - }
623   -
624   - if (commandContextFactory != null) {
625   - String engineCfgKey = getEngineCfgKey();
626   - CommandContextInterceptor commandContextInterceptor = new CommandContextInterceptor(commandContextFactory,
627   - classLoader, useClassForNameClassLoading, clock, objectMapper);
628   - engineConfigurations.put(engineCfgKey, this);
629   - commandContextInterceptor.setEngineCfgKey(engineCfgKey);
630   - commandContextInterceptor.setEngineConfigurations(engineConfigurations);
631   - interceptors.add(commandContextInterceptor);
632   - }
633   -
634   - if (transactionContextFactory != null) {
635   - interceptors.add(new TransactionContextInterceptor(transactionContextFactory));
636   - }
637   -
638   - List<CommandInterceptor> additionalCommandInterceptors = getAdditionalDefaultCommandInterceptors();
639   - if (additionalCommandInterceptors != null) {
640   - interceptors.addAll(additionalCommandInterceptors);
641   - }
642   -
643   - defaultCommandInterceptors = interceptors;
644   - }
645   - return defaultCommandInterceptors;
646   - }
647   -
648   - public abstract String getEngineCfgKey();
649   -
650   - public abstract String getEngineScopeType();
651   -
652   - public List<CommandInterceptor> getAdditionalDefaultCommandInterceptors() {
653   - return null;
654   - }
655   -
656   - public void initCommandExecutor() {
657   - if (commandExecutor == null) {
658   - CommandInterceptor first = initInterceptorChain(commandInterceptors);
659   - commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
660   - }
661   - }
662   -
663   - public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
664   - if (chain == null || chain.isEmpty()) {
665   - throw new FlowableException("invalid command interceptor chain configuration: " + chain);
666   - }
667   - for (int i = 0; i < chain.size() - 1; i++) {
668   - chain.get(i).setNext(chain.get(i + 1));
669   - }
670   - return chain.get(0);
671   - }
672   -
673   - public abstract CommandInterceptor createTransactionInterceptor();
674   -
675   -
676   - public void initBeans() {
677   - if (beans == null) {
678   - beans = new HashMap<>();
679   - }
680   - }
681   -
682   - // id generator
683   - // /////////////////////////////////////////////////////////////
684   -
685   - public void initIdGenerator() {
686   - if (idGenerator == null) {
687   - idGenerator = new StrongUuidGenerator();
688   - }
689   - }
690   -
691   - public void initObjectMapper() {
692   - if (objectMapper == null) {
693   - objectMapper = new ObjectMapper();
694   - objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
695   - }
696   - }
697   -
698   - public void initClock() {
699   - if (clock == null) {
700   - clock = new DefaultClockImpl();
701   - }
702   - }
703   -
704   - // Data managers ///////////////////////////////////////////////////////////
705   -
706   - public void initDataManagers() {
707   - if (propertyDataManager == null) {
708   - propertyDataManager = new MybatisPropertyDataManager(idGenerator);
709   - }
710   -
711   - if (byteArrayDataManager == null) {
712   - byteArrayDataManager = new MybatisByteArrayDataManager(idGenerator);
713   - }
714   - }
715   -
716   - // Entity managers //////////////////////////////////////////////////////////
717   -
718   - public void initEntityManagers() {
719   - if (propertyEntityManager == null) {
720   - propertyEntityManager = new PropertyEntityManagerImpl(this, propertyDataManager);
721   - }
722   -
723   - if (byteArrayEntityManager == null) {
724   - byteArrayEntityManager = new ByteArrayEntityManagerImpl(byteArrayDataManager, getEngineCfgKey(), this::getEventDispatcher);
725   - }
726   -
727   - if (tableDataManager == null) {
728   - tableDataManager = new TableDataManagerImpl(this);
729   - }
730   - }
731   -
732   - // services
733   - // /////////////////////////////////////////////////////////////////
734   -
735   - protected void initService(Object service) {
736   - if (service instanceof CommonEngineServiceImpl) {
737   - ((CommonEngineServiceImpl) service).setCommandExecutor(commandExecutor);
738   - }
739   - }
740   -
741   - // myBatis SqlSessionFactory
742   - // ////////////////////////////////////////////////
743   -
744   - public void initSessionFactories() {
745   - if (sessionFactories == null) {
746   - sessionFactories = new HashMap<>();
747   -
748   - if (usingRelationalDatabase) {
749   - initDbSqlSessionFactory();
750   - }
751   -
752   - addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class));
753   -
754   - if (isLoggingSessionEnabled()) {
755   - if (!sessionFactories.containsKey(LoggingSession.class)) {
756   - LoggingSessionFactory loggingSessionFactory = new LoggingSessionFactory();
757   - loggingSessionFactory.setLoggingListener(loggingListener);
758   - loggingSessionFactory.setObjectMapper(objectMapper);
759   - sessionFactories.put(LoggingSession.class, loggingSessionFactory);
760   - }
761   - }
762   -
763   - commandContextFactory.setSessionFactories(sessionFactories);
764   -
765   - } else {
766   - if (usingRelationalDatabase) {
767   - initDbSqlSessionFactoryEntitySettings();
768   - }
769   - }
770   -
771   - if (customSessionFactories != null) {
772   - for (SessionFactory sessionFactory : customSessionFactories) {
773   - addSessionFactory(sessionFactory);
774   - }
775   - }
776   - }
777   -
778   - public void initDbSqlSessionFactory() {
779   - if (dbSqlSessionFactory == null) {
780   - dbSqlSessionFactory = createDbSqlSessionFactory();
781   - }
782   - dbSqlSessionFactory.setDatabaseType(databaseType);
783   - dbSqlSessionFactory.setSqlSessionFactory(sqlSessionFactory);
784   - dbSqlSessionFactory.setDbHistoryUsed(isDbHistoryUsed);
785   - dbSqlSessionFactory.setDatabaseTablePrefix(databaseTablePrefix);
786   - dbSqlSessionFactory.setTablePrefixIsSchema(tablePrefixIsSchema);
787   - dbSqlSessionFactory.setDatabaseCatalog(databaseCatalog);
788   - dbSqlSessionFactory.setDatabaseSchema(databaseSchema);
789   - dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(maxNrOfStatementsInBulkInsert);
790   -
791   - initDbSqlSessionFactoryEntitySettings();
792   -
793   - addSessionFactory(dbSqlSessionFactory);
794   - }
795   -
796   - public DbSqlSessionFactory createDbSqlSessionFactory() {
797   - return new DbSqlSessionFactory(usePrefixId);
798   - }
799   -
800   - protected abstract void initDbSqlSessionFactoryEntitySettings();
801   -
802   - protected void defaultInitDbSqlSessionFactoryEntitySettings(List<Class<? extends Entity>> insertOrder, List<Class<? extends Entity>> deleteOrder) {
803   - if (insertOrder != null) {
804   - for (Class<? extends Entity> clazz : insertOrder) {
805   - dbSqlSessionFactory.getInsertionOrder().add(clazz);
806   -
807   - if (isBulkInsertEnabled) {
808   - dbSqlSessionFactory.getBulkInserteableEntityClasses().add(clazz);
809   - }
810   - }
811   - }
812   -
813   - if (deleteOrder != null) {
814   - for (Class<? extends Entity> clazz : deleteOrder) {
815   - dbSqlSessionFactory.getDeletionOrder().add(clazz);
816   - }
817   - }
818   - }
819   -
820   - public void initTransactionFactory() {
821   - if (transactionFactory == null) {
822   - if (transactionsExternallyManaged) {
823   - transactionFactory = new ManagedTransactionFactory();
824   - Properties properties = new Properties();
825   - properties.put("closeConnection", "false");
826   - this.transactionFactory.setProperties(properties);
827   - } else {
828   - transactionFactory = new JdbcTransactionFactory();
829   - }
830   - }
831   - }
832   -
833   - public void initSqlSessionFactory() {
834   - if (sqlSessionFactory == null) {
835   - InputStream inputStream = null;
836   - try {
837   - inputStream = getMyBatisXmlConfigurationStream();
838   -
839   - Environment environment = new Environment("default", transactionFactory, dataSource);
840   - Reader reader = new InputStreamReader(inputStream);
841   - Properties properties = new Properties();
842   - properties.put("prefix", databaseTablePrefix);
843   -
844   - String wildcardEscapeClause = "";
845   - if ((databaseWildcardEscapeCharacter != null) && (databaseWildcardEscapeCharacter.length() != 0)) {
846   - wildcardEscapeClause = " escape '" + databaseWildcardEscapeCharacter + "'";
847   - }
848   - properties.put("wildcardEscapeClause", wildcardEscapeClause);
849   -
850   - // set default properties
851   - properties.put("limitBefore", "");
852   - properties.put("limitAfter", "");
853   - properties.put("limitBetween", "");
854   - properties.put("limitBeforeNativeQuery", "");
855   - properties.put("limitAfterNativeQuery", "");
856   - properties.put("blobType", "BLOB");
857   - properties.put("boolValue", "TRUE");
858   -
859   - if (databaseType != null) {
860   - properties.load(getResourceAsStream(pathToEngineDbProperties()));
861   - }
862   -
863   - Configuration configuration = initMybatisConfiguration(environment, reader, properties);
864   - sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
865   -
866   - } catch (Exception e) {
867   - throw new FlowableException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e);
868   - } finally {
869   - IoUtil.closeSilently(inputStream);
870   - }
871   - } else {
872   - // This is needed when the SQL Session Factory is created by another engine.
873   - // When custom XML Mappers are registered with this engine they need to be loaded in the configuration as well
874   - applyCustomMybatisCustomizations(sqlSessionFactory.getConfiguration());
875   - }
876   - }
877   -
878   - public String pathToEngineDbProperties() {
879   - return "org/flowable/common/db/properties/" + databaseType + ".properties";
880   - }
881   -
882   - public Configuration initMybatisConfiguration(Environment environment, Reader reader, Properties properties) {
883   - XMLConfigBuilder parser = new XMLConfigBuilder(reader, "", properties);
884   - Configuration configuration = parser.getConfiguration();
885   -
886   - if (databaseType != null) {
887   - configuration.setDatabaseId(databaseType);
888   - }
889   -
890   - configuration.setEnvironment(environment);
891   -
892   - initMybatisTypeHandlers(configuration);
893   - initCustomMybatisInterceptors(configuration);
894   - if (isEnableLogSqlExecutionTime()) {
895   - initMyBatisLogSqlExecutionTimePlugin(configuration);
896   - }
897   -
898   - configuration = parseMybatisConfiguration(parser);
899   - return configuration;
900   - }
901   -
902   - public void initCustomMybatisMappers(Configuration configuration) {
903   - if (getCustomMybatisMappers() != null) {
904   - for (Class<?> clazz : getCustomMybatisMappers()) {
905   - if (!configuration.hasMapper(clazz)) {
906   - configuration.addMapper(clazz);
907   - }
908   - }
909   - }
910   - }
911   -
912   - public void initMybatisTypeHandlers(Configuration configuration) {
913   - // When mapping into Map<String, Object> there is currently a problem with MyBatis.
914   - // It will return objects which are driver specific.
915   - // Therefore we are registering the mappings between Object.class and the specific jdbc type here.
916   - // see https://github.com/mybatis/mybatis-3/issues/2216 for more info
917   - TypeHandlerRegistry handlerRegistry = configuration.getTypeHandlerRegistry();
918   -
919   - handlerRegistry.register(Object.class, JdbcType.BOOLEAN, new BooleanTypeHandler());
920   - handlerRegistry.register(Object.class, JdbcType.BIT, new BooleanTypeHandler());
921   -
922   - handlerRegistry.register(Object.class, JdbcType.TINYINT, new ByteTypeHandler());
923   -
924   - handlerRegistry.register(Object.class, JdbcType.SMALLINT, new ShortTypeHandler());
925   -
926   - handlerRegistry.register(Object.class, JdbcType.INTEGER, new IntegerTypeHandler());
927   -
928   - handlerRegistry.register(Object.class, JdbcType.FLOAT, new FloatTypeHandler());
929   -
930   - handlerRegistry.register(Object.class, JdbcType.DOUBLE, new DoubleTypeHandler());
931   -
932   - handlerRegistry.register(Object.class, JdbcType.CHAR, new StringTypeHandler());
933   - handlerRegistry.register(Object.class, JdbcType.CLOB, new ClobTypeHandler());
934   - handlerRegistry.register(Object.class, JdbcType.VARCHAR, new StringTypeHandler());
935   - handlerRegistry.register(Object.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
936   - handlerRegistry.register(Object.class, JdbcType.NVARCHAR, new NStringTypeHandler());
937   - handlerRegistry.register(Object.class, JdbcType.NCHAR, new NStringTypeHandler());
938   - handlerRegistry.register(Object.class, JdbcType.NCLOB, new NClobTypeHandler());
939   -
940   - handlerRegistry.register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
941   -
942   - handlerRegistry.register(Object.class, JdbcType.BIGINT, new LongTypeHandler());
943   -
944   - handlerRegistry.register(Object.class, JdbcType.REAL, new BigDecimalTypeHandler());
945   - handlerRegistry.register(Object.class, JdbcType.DECIMAL, new BigDecimalTypeHandler());
946   - handlerRegistry.register(Object.class, JdbcType.NUMERIC, new BigDecimalTypeHandler());
947   -
948   - handlerRegistry.register(Object.class, JdbcType.BLOB, new BlobInputStreamTypeHandler());
949   - handlerRegistry.register(Object.class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
950   -
951   - handlerRegistry.register(Object.class, JdbcType.DATE, new DateOnlyTypeHandler());
952   - handlerRegistry.register(Object.class, JdbcType.TIME, new TimeOnlyTypeHandler());
953   - handlerRegistry.register(Object.class, JdbcType.TIMESTAMP, new DateTypeHandler());
954   -
955   - handlerRegistry.register(Object.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
956   - }
957   -
958   - public void initCustomMybatisInterceptors(Configuration configuration) {
959   - if (customMybatisInterceptors!=null){
960   - for (Interceptor interceptor :customMybatisInterceptors){
961   - configuration.addInterceptor(interceptor);
962   - }
963   - }
964   - }
965   -
966   - public void initMyBatisLogSqlExecutionTimePlugin(Configuration configuration) {
967   - configuration.addInterceptor(new LogSqlExecutionTimePlugin());
968   - }
969   -
970   - public Configuration parseMybatisConfiguration(XMLConfigBuilder parser) {
971   - Configuration configuration = parser.parse();
972   -
973   - applyCustomMybatisCustomizations(configuration);
974   - return configuration;
975   - }
976   -
977   - protected void applyCustomMybatisCustomizations(Configuration configuration) {
978   - initCustomMybatisMappers(configuration);
979   -
980   - if (dependentEngineMybatisTypeAliasConfigs != null) {
981   - for (MybatisTypeAliasConfigurator typeAliasConfig : dependentEngineMybatisTypeAliasConfigs) {
982   - typeAliasConfig.configure(configuration.getTypeAliasRegistry());
983   - }
984   - }
985   - if (dependentEngineMybatisTypeHandlerConfigs != null) {
986   - for (MybatisTypeHandlerConfigurator typeHandlerConfig : dependentEngineMybatisTypeHandlerConfigs) {
987   - typeHandlerConfig.configure(configuration.getTypeHandlerRegistry());
988   - }
989   - }
990   -
991   - parseDependentEngineMybatisXMLMappers(configuration);
992   - parseCustomMybatisXMLMappers(configuration);
993   - }
994   -
995   - public void parseCustomMybatisXMLMappers(Configuration configuration) {
996   - if (getCustomMybatisXMLMappers() != null) {
997   - for (String resource : getCustomMybatisXMLMappers()) {
998   - parseMybatisXmlMapping(configuration, resource);
999   - }
1000   - }
1001   - }
1002   -
1003   - public void parseDependentEngineMybatisXMLMappers(Configuration configuration) {
1004   - if (getDependentEngineMyBatisXmlMappers() != null) {
1005   - for (String resource : getDependentEngineMyBatisXmlMappers()) {
1006   - parseMybatisXmlMapping(configuration, resource);
1007   - }
1008   - }
1009   - }
1010   -
1011   - protected void parseMybatisXmlMapping(Configuration configuration, String resource) {
1012   - // see XMLConfigBuilder.mapperElement()
1013   - XMLMapperBuilder mapperParser = new XMLMapperBuilder(getResourceAsStream(resource), configuration, resource, configuration.getSqlFragments());
1014   - mapperParser.parse();
1015   - }
1016   -
1017   - protected InputStream getResourceAsStream(String resource) {
1018   - ClassLoader classLoader = getClassLoader();
1019   - if (classLoader != null) {
1020   - return getClassLoader().getResourceAsStream(resource);
1021   - } else {
1022   - return this.getClass().getClassLoader().getResourceAsStream(resource);
1023   - }
1024   - }
1025   -
1026   - public void setMybatisMappingFile(String file) {
1027   - this.mybatisMappingFile = file;
1028   - }
1029   -
1030   - public String getMybatisMappingFile() {
1031   - return mybatisMappingFile;
1032   - }
1033   -
1034   - public abstract InputStream getMyBatisXmlConfigurationStream();
1035   -
1036   - public void initConfigurators() {
1037   -
1038   - allConfigurators = new ArrayList<>();
1039   - allConfigurators.addAll(getEngineSpecificEngineConfigurators());
1040   -
1041   - // Configurators that are explicitly added to the config
1042   - if (configurators != null) {
1043   - allConfigurators.addAll(configurators);
1044   - }
1045   -
1046   - // Auto discovery through ServiceLoader
1047   - if (enableConfiguratorServiceLoader) {
1048   - ClassLoader classLoader = getClassLoader();
1049   - if (classLoader == null) {
1050   - classLoader = ReflectUtil.getClassLoader();
1051   - }
1052   -
1053   - ServiceLoader<EngineConfigurator> configuratorServiceLoader = ServiceLoader.load(EngineConfigurator.class, classLoader);
1054   - int nrOfServiceLoadedConfigurators = 0;
1055   - for (EngineConfigurator configurator : configuratorServiceLoader) {
1056   - allConfigurators.add(configurator);
1057   - nrOfServiceLoadedConfigurators++;
1058   - }
1059   -
1060   - if (nrOfServiceLoadedConfigurators > 0) {
1061   - logger.info("Found {} auto-discoverable Process Engine Configurator{}", nrOfServiceLoadedConfigurators, nrOfServiceLoadedConfigurators > 1 ? "s" : "");
1062   - }
1063   -
1064   - if (!allConfigurators.isEmpty()) {
1065   -
1066   - // Order them according to the priorities (useful for dependent
1067   - // configurator)
1068   - allConfigurators.sort(new Comparator<EngineConfigurator>() {
1069   -
1070   - @Override
1071   - public int compare(EngineConfigurator configurator1, EngineConfigurator configurator2) {
1072   - int priority1 = configurator1.getPriority();
1073   - int priority2 = configurator2.getPriority();
1074   -
1075   - if (priority1 < priority2) {
1076   - return -1;
1077   - } else if (priority1 > priority2) {
1078   - return 1;
1079   - }
1080   - return 0;
1081   - }
1082   - });
1083   -
1084   - // Execute the configurators
1085   - logger.info("Found {} Engine Configurators in total:", allConfigurators.size());
1086   - for (EngineConfigurator configurator : allConfigurators) {
1087   - logger.info("{} (priority:{})", configurator.getClass(), configurator.getPriority());
1088   - }
1089   -
1090   - }
1091   -
1092   - }
1093   - }
1094   -
1095   - public void close() {
1096   - if (forceCloseMybatisConnectionPool && dataSource instanceof PooledDataSource) {
1097   - /*
1098   - * When the datasource is created by a Flowable engine (i.e. it's an instance of PooledDataSource),
1099   - * the connection pool needs to be closed when closing the engine.
1100   - * Note that calling forceCloseAll() multiple times (as is the case when running with multiple engine) is ok.
1101   - */
1102   - ((PooledDataSource) dataSource).forceCloseAll();
1103   - }
1104   - }
1105   -
1106   - protected List<EngineConfigurator> getEngineSpecificEngineConfigurators() {
1107   - // meant to be overridden if needed
1108   - return Collections.emptyList();
1109   - }
1110   -
1111   - public void configuratorsBeforeInit() {
1112   - for (EngineConfigurator configurator : allConfigurators) {
1113   - logger.info("Executing beforeInit() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
1114   - configurator.beforeInit(this);
1115   - }
1116   - }
1117   -
1118   - public void configuratorsAfterInit() {
1119   - for (EngineConfigurator configurator : allConfigurators) {
1120   - logger.info("Executing configure() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
1121   - configurator.configure(this);
1122   - }
1123   - }
1124   -
1125   - public LockManager getLockManager(String lockName) {
1126   - return new LockManagerImpl(commandExecutor, lockName, getLockPollRate(), getEngineCfgKey());
1127   - }
1128   -
1129   - // getters and setters
1130   - // //////////////////////////////////////////////////////
1131   -
1132   - public abstract String getEngineName();
1133   -
1134   - public ClassLoader getClassLoader() {
1135   - return classLoader;
1136   - }
1137   -
1138   - public AbstractEngineConfiguration setClassLoader(ClassLoader classLoader) {
1139   - this.classLoader = classLoader;
1140   - return this;
1141   - }
1142   -
1143   - public boolean isUseClassForNameClassLoading() {
1144   - return useClassForNameClassLoading;
1145   - }
1146   -
1147   - public AbstractEngineConfiguration setUseClassForNameClassLoading(boolean useClassForNameClassLoading) {
1148   - this.useClassForNameClassLoading = useClassForNameClassLoading;
1149   - return this;
1150   - }
1151   -
1152   - public void addEngineLifecycleListener(EngineLifecycleListener engineLifecycleListener) {
1153   - if (this.engineLifecycleListeners == null) {
1154   - this.engineLifecycleListeners = new ArrayList<>();
1155   - }
1156   - this.engineLifecycleListeners.add(engineLifecycleListener);
1157   - }
1158   -
1159   - public List<EngineLifecycleListener> getEngineLifecycleListeners() {
1160   - return engineLifecycleListeners;
1161   - }
1162   -
1163   - public AbstractEngineConfiguration setEngineLifecycleListeners(List<EngineLifecycleListener> engineLifecycleListeners) {
1164   - this.engineLifecycleListeners = engineLifecycleListeners;
1165   - return this;
1166   - }
1167   -
1168   - public String getDatabaseType() {
1169   - return databaseType;
1170   - }
1171   -
1172   - public AbstractEngineConfiguration setDatabaseType(String databaseType) {
1173   - this.databaseType = databaseType;
1174   - return this;
1175   - }
1176   -
1177   - public DataSource getDataSource() {
1178   - return dataSource;
1179   - }
1180   -
1181   - public AbstractEngineConfiguration setDataSource(DataSource dataSource) {
1182   - this.dataSource = dataSource;
1183   - return this;
1184   - }
1185   -
1186   - public SchemaManager getSchemaManager() {
1187   - return schemaManager;
1188   - }
1189   -
1190   - public AbstractEngineConfiguration setSchemaManager(SchemaManager schemaManager) {
1191   - this.schemaManager = schemaManager;
1192   - return this;
1193   - }
1194   -
1195   - public SchemaManager getCommonSchemaManager() {
1196   - return commonSchemaManager;
1197   - }
1198   -
1199   - public AbstractEngineConfiguration setCommonSchemaManager(SchemaManager commonSchemaManager) {
1200   - this.commonSchemaManager = commonSchemaManager;
1201   - return this;
1202   - }
1203   -
1204   - public Command<Void> getSchemaManagementCmd() {
1205   - return schemaManagementCmd;
1206   - }
1207   -
1208   - public AbstractEngineConfiguration setSchemaManagementCmd(Command<Void> schemaManagementCmd) {
1209   - this.schemaManagementCmd = schemaManagementCmd;
1210   - return this;
1211   - }
1212   -
1213   - public String getJdbcDriver() {
1214   - return jdbcDriver;
1215   - }
1216   -
1217   - public AbstractEngineConfiguration setJdbcDriver(String jdbcDriver) {
1218   - this.jdbcDriver = jdbcDriver;
1219   - return this;
1220   - }
1221   -
1222   - public String getJdbcUrl() {
1223   - return jdbcUrl;
1224   - }
1225   -
1226   - public AbstractEngineConfiguration setJdbcUrl(String jdbcUrl) {
1227   - this.jdbcUrl = jdbcUrl;
1228   - return this;
1229   - }
1230   -
1231   - public String getJdbcUsername() {
1232   - return jdbcUsername;
1233   - }
1234   -
1235   - public AbstractEngineConfiguration setJdbcUsername(String jdbcUsername) {
1236   - this.jdbcUsername = jdbcUsername;
1237   - return this;
1238   - }
1239   -
1240   - public String getJdbcPassword() {
1241   - return jdbcPassword;
1242   - }
1243   -
1244   - public AbstractEngineConfiguration setJdbcPassword(String jdbcPassword) {
1245   - this.jdbcPassword = jdbcPassword;
1246   - return this;
1247   - }
1248   -
1249   - public int getJdbcMaxActiveConnections() {
1250   - return jdbcMaxActiveConnections;
1251   - }
1252   -
1253   - public AbstractEngineConfiguration setJdbcMaxActiveConnections(int jdbcMaxActiveConnections) {
1254   - this.jdbcMaxActiveConnections = jdbcMaxActiveConnections;
1255   - return this;
1256   - }
1257   -
1258   - public int getJdbcMaxIdleConnections() {
1259   - return jdbcMaxIdleConnections;
1260   - }
1261   -
1262   - public AbstractEngineConfiguration setJdbcMaxIdleConnections(int jdbcMaxIdleConnections) {
1263   - this.jdbcMaxIdleConnections = jdbcMaxIdleConnections;
1264   - return this;
1265   - }
1266   -
1267   - public int getJdbcMaxCheckoutTime() {
1268   - return jdbcMaxCheckoutTime;
1269   - }
1270   -
1271   - public AbstractEngineConfiguration setJdbcMaxCheckoutTime(int jdbcMaxCheckoutTime) {
1272   - this.jdbcMaxCheckoutTime = jdbcMaxCheckoutTime;
1273   - return this;
1274   - }
1275   -
1276   - public int getJdbcMaxWaitTime() {
1277   - return jdbcMaxWaitTime;
1278   - }
1279   -
1280   - public AbstractEngineConfiguration setJdbcMaxWaitTime(int jdbcMaxWaitTime) {
1281   - this.jdbcMaxWaitTime = jdbcMaxWaitTime;
1282   - return this;
1283   - }
1284   -
1285   - public boolean isJdbcPingEnabled() {
1286   - return jdbcPingEnabled;
1287   - }
1288   -
1289   - public AbstractEngineConfiguration setJdbcPingEnabled(boolean jdbcPingEnabled) {
1290   - this.jdbcPingEnabled = jdbcPingEnabled;
1291   - return this;
1292   - }
1293   -
1294   - public int getJdbcPingConnectionNotUsedFor() {
1295   - return jdbcPingConnectionNotUsedFor;
1296   - }
1297   -
1298   - public AbstractEngineConfiguration setJdbcPingConnectionNotUsedFor(int jdbcPingConnectionNotUsedFor) {
1299   - this.jdbcPingConnectionNotUsedFor = jdbcPingConnectionNotUsedFor;
1300   - return this;
1301   - }
1302   -
1303   - public int getJdbcDefaultTransactionIsolationLevel() {
1304   - return jdbcDefaultTransactionIsolationLevel;
1305   - }
1306   -
1307   - public AbstractEngineConfiguration setJdbcDefaultTransactionIsolationLevel(int jdbcDefaultTransactionIsolationLevel) {
1308   - this.jdbcDefaultTransactionIsolationLevel = jdbcDefaultTransactionIsolationLevel;
1309   - return this;
1310   - }
1311   -
1312   - public String getJdbcPingQuery() {
1313   - return jdbcPingQuery;
1314   - }
1315   -
1316   - public AbstractEngineConfiguration setJdbcPingQuery(String jdbcPingQuery) {
1317   - this.jdbcPingQuery = jdbcPingQuery;
1318   - return this;
1319   - }
1320   -
1321   - public String getDataSourceJndiName() {
1322   - return dataSourceJndiName;
1323   - }
1324   -
1325   - public AbstractEngineConfiguration setDataSourceJndiName(String dataSourceJndiName) {
1326   - this.dataSourceJndiName = dataSourceJndiName;
1327   - return this;
1328   - }
1329   -
1330   - public CommandConfig getSchemaCommandConfig() {
1331   - return schemaCommandConfig;
1332   - }
1333   -
1334   - public AbstractEngineConfiguration setSchemaCommandConfig(CommandConfig schemaCommandConfig) {
1335   - this.schemaCommandConfig = schemaCommandConfig;
1336   - return this;
1337   - }
1338   -
1339   - public boolean isTransactionsExternallyManaged() {
1340   - return transactionsExternallyManaged;
1341   - }
1342   -
1343   - public AbstractEngineConfiguration setTransactionsExternallyManaged(boolean transactionsExternallyManaged) {
1344   - this.transactionsExternallyManaged = transactionsExternallyManaged;
1345   - return this;
1346   - }
1347   -
1348   - public Map<Object, Object> getBeans() {
1349   - return beans;
1350   - }
1351   -
1352   - public AbstractEngineConfiguration setBeans(Map<Object, Object> beans) {
1353   - this.beans = beans;
1354   - return this;
1355   - }
1356   -
1357   - public IdGenerator getIdGenerator() {
1358   - return idGenerator;
1359   - }
1360   -
1361   - public AbstractEngineConfiguration setIdGenerator(IdGenerator idGenerator) {
1362   - this.idGenerator = idGenerator;
1363   - return this;
1364   - }
1365   -
1366   - public boolean isUsePrefixId() {
1367   - return usePrefixId;
1368   - }
1369   -
1370   - public AbstractEngineConfiguration setUsePrefixId(boolean usePrefixId) {
1371   - this.usePrefixId = usePrefixId;
1372   - return this;
1373   - }
1374   -
1375   - public String getXmlEncoding() {
1376   - return xmlEncoding;
1377   - }
1378   -
1379   - public AbstractEngineConfiguration setXmlEncoding(String xmlEncoding) {
1380   - this.xmlEncoding = xmlEncoding;
1381   - return this;
1382   - }
1383   -
1384   - public CommandConfig getDefaultCommandConfig() {
1385   - return defaultCommandConfig;
1386   - }
1387   -
1388   - public AbstractEngineConfiguration setDefaultCommandConfig(CommandConfig defaultCommandConfig) {
1389   - this.defaultCommandConfig = defaultCommandConfig;
1390   - return this;
1391   - }
1392   -
1393   - public CommandExecutor getCommandExecutor() {
1394   - return commandExecutor;
1395   - }
1396   -
1397   - public AbstractEngineConfiguration setCommandExecutor(CommandExecutor commandExecutor) {
1398   - this.commandExecutor = commandExecutor;
1399   - return this;
1400   - }
1401   -
1402   - public CommandContextFactory getCommandContextFactory() {
1403   - return commandContextFactory;
1404   - }
1405   -
1406   - public AbstractEngineConfiguration setCommandContextFactory(CommandContextFactory commandContextFactory) {
1407   - this.commandContextFactory = commandContextFactory;
1408   - return this;
1409   - }
1410   -
1411   - public CommandInterceptor getCommandInvoker() {
1412   - return commandInvoker;
1413   - }
1414   -
1415   - public AbstractEngineConfiguration setCommandInvoker(CommandInterceptor commandInvoker) {
1416   - this.commandInvoker = commandInvoker;
1417   - return this;
1418   - }
1419   -
1420   - public AgendaOperationRunner getAgendaOperationRunner() {
1421   - return agendaOperationRunner;
1422   - }
1423   -
1424   - public AbstractEngineConfiguration setAgendaOperationRunner(AgendaOperationRunner agendaOperationRunner) {
1425   - this.agendaOperationRunner = agendaOperationRunner;
1426   - return this;
1427   - }
1428   -
1429   - public List<CommandInterceptor> getCustomPreCommandInterceptors() {
1430   - return customPreCommandInterceptors;
1431   - }
1432   -
1433   - public AbstractEngineConfiguration setCustomPreCommandInterceptors(List<CommandInterceptor> customPreCommandInterceptors) {
1434   - this.customPreCommandInterceptors = customPreCommandInterceptors;
1435   - return this;
1436   - }
1437   -
1438   - public List<CommandInterceptor> getCustomPostCommandInterceptors() {
1439   - return customPostCommandInterceptors;
1440   - }
1441   -
1442   - public AbstractEngineConfiguration setCustomPostCommandInterceptors(List<CommandInterceptor> customPostCommandInterceptors) {
1443   - this.customPostCommandInterceptors = customPostCommandInterceptors;
1444   - return this;
1445   - }
1446   -
1447   - public List<CommandInterceptor> getCommandInterceptors() {
1448   - return commandInterceptors;
1449   - }
1450   -
1451   - public AbstractEngineConfiguration setCommandInterceptors(List<CommandInterceptor> commandInterceptors) {
1452   - this.commandInterceptors = commandInterceptors;
1453   - return this;
1454   - }
1455   -
1456   - public Map<String, AbstractEngineConfiguration> getEngineConfigurations() {
1457   - return engineConfigurations;
1458   - }
1459   -
1460   - public AbstractEngineConfiguration setEngineConfigurations(Map<String, AbstractEngineConfiguration> engineConfigurations) {
1461   - this.engineConfigurations = engineConfigurations;
1462   - return this;
1463   - }
1464   -
1465   - public void addEngineConfiguration(String key, String scopeType, AbstractEngineConfiguration engineConfiguration) {
1466   - if (engineConfigurations == null) {
1467   - engineConfigurations = new HashMap<>();
1468   - }
1469   - engineConfigurations.put(key, engineConfiguration);
1470   - engineConfigurations.put(scopeType, engineConfiguration);
1471   - }
1472   -
1473   - public Map<String, AbstractServiceConfiguration> getServiceConfigurations() {
1474   - return serviceConfigurations;
1475   - }
1476   -
1477   - public AbstractEngineConfiguration setServiceConfigurations(Map<String, AbstractServiceConfiguration> serviceConfigurations) {
1478   - this.serviceConfigurations = serviceConfigurations;
1479   - return this;
1480   - }
1481   -
1482   - public void addServiceConfiguration(String key, AbstractServiceConfiguration serviceConfiguration) {
1483   - if (serviceConfigurations == null) {
1484   - serviceConfigurations = new HashMap<>();
1485   - }
1486   - serviceConfigurations.put(key, serviceConfiguration);
1487   - }
1488   -
1489   - public Map<String, EventRegistryEventConsumer> getEventRegistryEventConsumers() {
1490   - return eventRegistryEventConsumers;
1491   - }
1492   -
1493   - public AbstractEngineConfiguration setEventRegistryEventConsumers(Map<String, EventRegistryEventConsumer> eventRegistryEventConsumers) {
1494   - this.eventRegistryEventConsumers = eventRegistryEventConsumers;
1495   - return this;
1496   - }
1497   -
1498   - public void addEventRegistryEventConsumer(String key, EventRegistryEventConsumer eventRegistryEventConsumer) {
1499   - if (eventRegistryEventConsumers == null) {
1500   - eventRegistryEventConsumers = new HashMap<>();
1501   - }
1502   - eventRegistryEventConsumers.put(key, eventRegistryEventConsumer);
1503   - }
1504   -
1505   - public AbstractEngineConfiguration setDefaultCommandInterceptors(Collection<? extends CommandInterceptor> defaultCommandInterceptors) {
1506   - this.defaultCommandInterceptors = defaultCommandInterceptors;
1507   - return this;
1508   - }
1509   -
1510   - public SqlSessionFactory getSqlSessionFactory() {
1511   - return sqlSessionFactory;
1512   - }
1513   -
1514   - public AbstractEngineConfiguration setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
1515   - this.sqlSessionFactory = sqlSessionFactory;
1516   - return this;
1517   - }
1518   -
1519   - public boolean isDbHistoryUsed() {
1520   - return isDbHistoryUsed;
1521   - }
1522   -
1523   - public AbstractEngineConfiguration setDbHistoryUsed(boolean isDbHistoryUsed) {
1524   - this.isDbHistoryUsed = isDbHistoryUsed;
1525   - return this;
1526   - }
1527   -
1528   - public DbSqlSessionFactory getDbSqlSessionFactory() {
1529   - return dbSqlSessionFactory;
1530   - }
1531   -
1532   - public AbstractEngineConfiguration setDbSqlSessionFactory(DbSqlSessionFactory dbSqlSessionFactory) {
1533   - this.dbSqlSessionFactory = dbSqlSessionFactory;
1534   - return this;
1535   - }
1536   -
1537   - public TransactionFactory getTransactionFactory() {
1538   - return transactionFactory;
1539   - }
1540   -
1541   - public AbstractEngineConfiguration setTransactionFactory(TransactionFactory transactionFactory) {
1542   - this.transactionFactory = transactionFactory;
1543   - return this;
1544   - }
1545   -
1546   - public TransactionContextFactory getTransactionContextFactory() {
1547   - return transactionContextFactory;
1548   - }
1549   -
1550   - public AbstractEngineConfiguration setTransactionContextFactory(TransactionContextFactory transactionContextFactory) {
1551   - this.transactionContextFactory = transactionContextFactory;
1552   - return this;
1553   - }
1554   -
1555   - public int getMaxNrOfStatementsInBulkInsert() {
1556   - return maxNrOfStatementsInBulkInsert;
1557   - }
1558   -
1559   - public AbstractEngineConfiguration setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) {
1560   - this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert;
1561   - return this;
1562   - }
1563   -
1564   - public boolean isBulkInsertEnabled() {
1565   - return isBulkInsertEnabled;
1566   - }
1567   -
1568   - public AbstractEngineConfiguration setBulkInsertEnabled(boolean isBulkInsertEnabled) {
1569   - this.isBulkInsertEnabled = isBulkInsertEnabled;
1570   - return this;
1571   - }
1572   -
1573   - public Set<Class<?>> getCustomMybatisMappers() {
1574   - return customMybatisMappers;
1575   - }
1576   -
1577   - public AbstractEngineConfiguration setCustomMybatisMappers(Set<Class<?>> customMybatisMappers) {
1578   - this.customMybatisMappers = customMybatisMappers;
1579   - return this;
1580   - }
1581   -
1582   - public Set<String> getCustomMybatisXMLMappers() {
1583   - return customMybatisXMLMappers;
1584   - }
1585   -
1586   - public AbstractEngineConfiguration setCustomMybatisXMLMappers(Set<String> customMybatisXMLMappers) {
1587   - this.customMybatisXMLMappers = customMybatisXMLMappers;
1588   - return this;
1589   - }
1590   -
1591   - public Set<String> getDependentEngineMyBatisXmlMappers() {
1592   - return dependentEngineMyBatisXmlMappers;
1593   - }
1594   -
1595   - public AbstractEngineConfiguration setCustomMybatisInterceptors(List<Interceptor> customMybatisInterceptors) {
1596   - this.customMybatisInterceptors = customMybatisInterceptors;
1597   - return this;
1598   - }
1599   -
1600   - public List<Interceptor> getCustomMybatisInterceptors() {
1601   - return customMybatisInterceptors;
1602   - }
1603   -
1604   - public AbstractEngineConfiguration setDependentEngineMyBatisXmlMappers(Set<String> dependentEngineMyBatisXmlMappers) {
1605   - this.dependentEngineMyBatisXmlMappers = dependentEngineMyBatisXmlMappers;
1606   - return this;
1607   - }
1608   -
1609   - public List<MybatisTypeAliasConfigurator> getDependentEngineMybatisTypeAliasConfigs() {
1610   - return dependentEngineMybatisTypeAliasConfigs;
1611   - }
1612   -
1613   - public AbstractEngineConfiguration setDependentEngineMybatisTypeAliasConfigs(List<MybatisTypeAliasConfigurator> dependentEngineMybatisTypeAliasConfigs) {
1614   - this.dependentEngineMybatisTypeAliasConfigs = dependentEngineMybatisTypeAliasConfigs;
1615   - return this;
1616   - }
1617   -
1618   - public List<MybatisTypeHandlerConfigurator> getDependentEngineMybatisTypeHandlerConfigs() {
1619   - return dependentEngineMybatisTypeHandlerConfigs;
1620   - }
1621   -
1622   - public AbstractEngineConfiguration setDependentEngineMybatisTypeHandlerConfigs(List<MybatisTypeHandlerConfigurator> dependentEngineMybatisTypeHandlerConfigs) {
1623   - this.dependentEngineMybatisTypeHandlerConfigs = dependentEngineMybatisTypeHandlerConfigs;
1624   - return this;
1625   - }
1626   -
1627   - public List<SessionFactory> getCustomSessionFactories() {
1628   - return customSessionFactories;
1629   - }
1630   -
1631   - public AbstractEngineConfiguration addCustomSessionFactory(SessionFactory sessionFactory) {
1632   - if (customSessionFactories == null) {
1633   - customSessionFactories = new ArrayList<>();
1634   - }
1635   - customSessionFactories.add(sessionFactory);
1636   - return this;
1637   - }
1638   -
1639   - public AbstractEngineConfiguration setCustomSessionFactories(List<SessionFactory> customSessionFactories) {
1640   - this.customSessionFactories = customSessionFactories;
1641   - return this;
1642   - }
1643   -
1644   - public boolean isUsingRelationalDatabase() {
1645   - return usingRelationalDatabase;
1646   - }
1647   -
1648   - public AbstractEngineConfiguration setUsingRelationalDatabase(boolean usingRelationalDatabase) {
1649   - this.usingRelationalDatabase = usingRelationalDatabase;
1650   - return this;
1651   - }
1652   -
1653   - public boolean isUsingSchemaMgmt() {
1654   - return usingSchemaMgmt;
1655   - }
1656   -
1657   - public AbstractEngineConfiguration setUsingSchemaMgmt(boolean usingSchema) {
1658   - this.usingSchemaMgmt = usingSchema;
1659   - return this;
1660   - }
1661   -
1662   - public String getDatabaseTablePrefix() {
1663   - return databaseTablePrefix;
1664   - }
1665   -
1666   - public AbstractEngineConfiguration setDatabaseTablePrefix(String databaseTablePrefix) {
1667   - this.databaseTablePrefix = databaseTablePrefix;
1668   - return this;
1669   - }
1670   -
1671   - public String getDatabaseWildcardEscapeCharacter() {
1672   - return databaseWildcardEscapeCharacter;
1673   - }
1674   -
1675   - public AbstractEngineConfiguration setDatabaseWildcardEscapeCharacter(String databaseWildcardEscapeCharacter) {
1676   - this.databaseWildcardEscapeCharacter = databaseWildcardEscapeCharacter;
1677   - return this;
1678   - }
1679   -
1680   - public String getDatabaseCatalog() {
1681   - return databaseCatalog;
1682   - }
1683   -
1684   - public AbstractEngineConfiguration setDatabaseCatalog(String databaseCatalog) {
1685   - this.databaseCatalog = databaseCatalog;
1686   - return this;
1687   - }
1688   -
1689   - public String getDatabaseSchema() {
1690   - return databaseSchema;
1691   - }
1692   -
1693   - public AbstractEngineConfiguration setDatabaseSchema(String databaseSchema) {
1694   - this.databaseSchema = databaseSchema;
1695   - return this;
1696   - }
1697   -
1698   - public boolean isTablePrefixIsSchema() {
1699   - return tablePrefixIsSchema;
1700   - }
1701   -
1702   - public AbstractEngineConfiguration setTablePrefixIsSchema(boolean tablePrefixIsSchema) {
1703   - this.tablePrefixIsSchema = tablePrefixIsSchema;
1704   - return this;
1705   - }
1706   -
1707   - public boolean isAlwaysLookupLatestDefinitionVersion() {
1708   - return alwaysLookupLatestDefinitionVersion;
1709   - }
1710   -
1711   - public AbstractEngineConfiguration setAlwaysLookupLatestDefinitionVersion(boolean alwaysLookupLatestDefinitionVersion) {
1712   - this.alwaysLookupLatestDefinitionVersion = alwaysLookupLatestDefinitionVersion;
1713   - return this;
1714   - }
1715   -
1716   - public boolean isFallbackToDefaultTenant() {
1717   - return fallbackToDefaultTenant;
1718   - }
1719   -
1720   - public AbstractEngineConfiguration setFallbackToDefaultTenant(boolean fallbackToDefaultTenant) {
1721   - this.fallbackToDefaultTenant = fallbackToDefaultTenant;
1722   - return this;
1723   - }
1724   -
1725   - /**
1726   - * @return name of the default tenant
1727   - * @deprecated use {@link AbstractEngineConfiguration#getDefaultTenantProvider()} instead
1728   - */
1729   - @Deprecated
1730   - public String getDefaultTenantValue() {
1731   - return getDefaultTenantProvider().getDefaultTenant(null, null, null);
1732   - }
1733   -
1734   - public AbstractEngineConfiguration setDefaultTenantValue(String defaultTenantValue) {
1735   - this.defaultTenantProvider = (tenantId, scope, scopeKey) -> defaultTenantValue;
1736   - return this;
1737   - }
1738   -
1739   - public DefaultTenantProvider getDefaultTenantProvider() {
1740   - return defaultTenantProvider;
1741   - }
1742   -
1743   - public AbstractEngineConfiguration setDefaultTenantProvider(DefaultTenantProvider defaultTenantProvider) {
1744   - this.defaultTenantProvider = defaultTenantProvider;
1745   - return this;
1746   - }
1747   -
1748   - public boolean isEnableLogSqlExecutionTime() {
1749   - return enableLogSqlExecutionTime;
1750   - }
1751   -
1752   - public void setEnableLogSqlExecutionTime(boolean enableLogSqlExecutionTime) {
1753   - this.enableLogSqlExecutionTime = enableLogSqlExecutionTime;
1754   - }
1755   -
1756   - public Map<Class<?>, SessionFactory> getSessionFactories() {
1757   - return sessionFactories;
1758   - }
1759   -
1760   - public AbstractEngineConfiguration setSessionFactories(Map<Class<?>, SessionFactory> sessionFactories) {
1761   - this.sessionFactories = sessionFactories;
1762   - return this;
1763   - }
1764   -
1765   - public String getDatabaseSchemaUpdate() {
1766   - return databaseSchemaUpdate;
1767   - }
1768   -
1769   - public AbstractEngineConfiguration setDatabaseSchemaUpdate(String databaseSchemaUpdate) {
1770   - this.databaseSchemaUpdate = databaseSchemaUpdate;
1771   - return this;
1772   - }
1773   -
1774   - public boolean isUseLockForDatabaseSchemaUpdate() {
1775   - return useLockForDatabaseSchemaUpdate;
1776   - }
1777   -
1778   - public AbstractEngineConfiguration setUseLockForDatabaseSchemaUpdate(boolean useLockForDatabaseSchemaUpdate) {
1779   - this.useLockForDatabaseSchemaUpdate = useLockForDatabaseSchemaUpdate;
1780   - return this;
1781   - }
1782   -
1783   - public boolean isEnableEventDispatcher() {
1784   - return enableEventDispatcher;
1785   - }
1786   -
1787   - public AbstractEngineConfiguration setEnableEventDispatcher(boolean enableEventDispatcher) {
1788   - this.enableEventDispatcher = enableEventDispatcher;
1789   - return this;
1790   - }
1791   -
1792   - public FlowableEventDispatcher getEventDispatcher() {
1793   - return eventDispatcher;
1794   - }
1795   -
1796   - public AbstractEngineConfiguration setEventDispatcher(FlowableEventDispatcher eventDispatcher) {
1797   - this.eventDispatcher = eventDispatcher;
1798   - return this;
1799   - }
1800   -
1801   - public List<FlowableEventListener> getEventListeners() {
1802   - return eventListeners;
1803   - }
1804   -
1805   - public AbstractEngineConfiguration setEventListeners(List<FlowableEventListener> eventListeners) {
1806   - this.eventListeners = eventListeners;
1807   - return this;
1808   - }
1809   -
1810   - public Map<String, List<FlowableEventListener>> getTypedEventListeners() {
1811   - return typedEventListeners;
1812   - }
1813   -
1814   - public AbstractEngineConfiguration setTypedEventListeners(Map<String, List<FlowableEventListener>> typedEventListeners) {
1815   - this.typedEventListeners = typedEventListeners;
1816   - return this;
1817   - }
1818   -
1819   - public List<EventDispatchAction> getAdditionalEventDispatchActions() {
1820   - return additionalEventDispatchActions;
1821   - }
1822   -
1823   - public AbstractEngineConfiguration setAdditionalEventDispatchActions(List<EventDispatchAction> additionalEventDispatchActions) {
1824   - this.additionalEventDispatchActions = additionalEventDispatchActions;
1825   - return this;
1826   - }
1827   -
1828   - public void initEventDispatcher() {
1829   - if (this.eventDispatcher == null) {
1830   - this.eventDispatcher = new FlowableEventDispatcherImpl();
1831   - }
1832   -
1833   - initAdditionalEventDispatchActions();
1834   -
1835   - this.eventDispatcher.setEnabled(enableEventDispatcher);
1836   -
1837   - initEventListeners();
1838   - initTypedEventListeners();
1839   - }
1840   -
1841   - protected void initEventListeners() {
1842   - if (eventListeners != null) {
1843   - for (FlowableEventListener listenerToAdd : eventListeners) {
1844   - this.eventDispatcher.addEventListener(listenerToAdd);
1845   - }
1846   - }
1847   - }
1848   -
1849   - protected void initAdditionalEventDispatchActions() {
1850   - if (this.additionalEventDispatchActions == null) {
1851   - this.additionalEventDispatchActions = new ArrayList<>();
1852   - }
1853   - }
1854   -
1855   - protected void initTypedEventListeners() {
1856   - if (typedEventListeners != null) {
1857   - for (Map.Entry<String, List<FlowableEventListener>> listenersToAdd : typedEventListeners.entrySet()) {
1858   - // Extract types from the given string
1859   - FlowableEngineEventType[] types = FlowableEngineEventType.getTypesFromString(listenersToAdd.getKey());
1860   -
1861   - for (FlowableEventListener listenerToAdd : listenersToAdd.getValue()) {
1862   - this.eventDispatcher.addEventListener(listenerToAdd, types);
1863   - }
1864   - }
1865   - }
1866   - }
1867   -
1868   - public boolean isLoggingSessionEnabled() {
1869   - return loggingListener != null;
1870   - }
1871   -
1872   - public LoggingListener getLoggingListener() {
1873   - return loggingListener;
1874   - }
1875   -
1876   - public void setLoggingListener(LoggingListener loggingListener) {
1877   - this.loggingListener = loggingListener;
1878   - }
1879   -
1880   - public Clock getClock() {
1881   - return clock;
1882   - }
1883   -
1884   - public AbstractEngineConfiguration setClock(Clock clock) {
1885   - this.clock = clock;
1886   - return this;
1887   - }
1888   -
1889   - public ObjectMapper getObjectMapper() {
1890   - return objectMapper;
1891   - }
1892   -
1893   - public AbstractEngineConfiguration setObjectMapper(ObjectMapper objectMapper) {
1894   - this.objectMapper = objectMapper;
1895   - return this;
1896   - }
1897   -
1898   - public int getMaxLengthString() {
1899   - if (maxLengthStringVariableType == -1) {
1900   - if ("oracle".equalsIgnoreCase(databaseType)) {
1901   - return DEFAULT_ORACLE_MAX_LENGTH_STRING;
1902   - } else {
1903   - return DEFAULT_GENERIC_MAX_LENGTH_STRING;
1904   - }
1905   - } else {
1906   - return maxLengthStringVariableType;
1907   - }
1908   - }
1909   -
1910   - public int getMaxLengthStringVariableType() {
1911   - return maxLengthStringVariableType;
1912   - }
1913   -
1914   - public AbstractEngineConfiguration setMaxLengthStringVariableType(int maxLengthStringVariableType) {
1915   - this.maxLengthStringVariableType = maxLengthStringVariableType;
1916   - return this;
1917   - }
1918   -
1919   - public PropertyDataManager getPropertyDataManager() {
1920   - return propertyDataManager;
1921   - }
1922   -
1923   - public Duration getLockPollRate() {
1924   - return lockPollRate;
1925   - }
1926   -
1927   - public AbstractEngineConfiguration setLockPollRate(Duration lockPollRate) {
1928   - this.lockPollRate = lockPollRate;
1929   - return this;
1930   - }
1931   -
1932   - public Duration getSchemaLockWaitTime() {
1933   - return schemaLockWaitTime;
1934   - }
1935   -
1936   - public void setSchemaLockWaitTime(Duration schemaLockWaitTime) {
1937   - this.schemaLockWaitTime = schemaLockWaitTime;
1938   - }
1939   -
1940   - public AbstractEngineConfiguration setPropertyDataManager(PropertyDataManager propertyDataManager) {
1941   - this.propertyDataManager = propertyDataManager;
1942   - return this;
1943   - }
1944   -
1945   - public PropertyEntityManager getPropertyEntityManager() {
1946   - return propertyEntityManager;
1947   - }
1948   -
1949   - public AbstractEngineConfiguration setPropertyEntityManager(PropertyEntityManager propertyEntityManager) {
1950   - this.propertyEntityManager = propertyEntityManager;
1951   - return this;
1952   - }
1953   -
1954   - public ByteArrayDataManager getByteArrayDataManager() {
1955   - return byteArrayDataManager;
1956   - }
1957   -
1958   - public AbstractEngineConfiguration setByteArrayDataManager(ByteArrayDataManager byteArrayDataManager) {
1959   - this.byteArrayDataManager = byteArrayDataManager;
1960   - return this;
1961   - }
1962   -
1963   - public ByteArrayEntityManager getByteArrayEntityManager() {
1964   - return byteArrayEntityManager;
1965   - }
1966   -
1967   - public AbstractEngineConfiguration setByteArrayEntityManager(ByteArrayEntityManager byteArrayEntityManager) {
1968   - this.byteArrayEntityManager = byteArrayEntityManager;
1969   - return this;
1970   - }
1971   -
1972   - public TableDataManager getTableDataManager() {
1973   - return tableDataManager;
1974   - }
1975   -
1976   - public AbstractEngineConfiguration setTableDataManager(TableDataManager tableDataManager) {
1977   - this.tableDataManager = tableDataManager;
1978   - return this;
1979   - }
1980   -
1981   - public List<EngineDeployer> getDeployers() {
1982   - return deployers;
1983   - }
1984   -
1985   - public AbstractEngineConfiguration setDeployers(List<EngineDeployer> deployers) {
1986   - this.deployers = deployers;
1987   - return this;
1988   - }
1989   -
1990   - public List<EngineDeployer> getCustomPreDeployers() {
1991   - return customPreDeployers;
1992   - }
1993   -
1994   - public AbstractEngineConfiguration setCustomPreDeployers(List<EngineDeployer> customPreDeployers) {
1995   - this.customPreDeployers = customPreDeployers;
1996   - return this;
1997   - }
1998   -
1999   - public List<EngineDeployer> getCustomPostDeployers() {
2000   - return customPostDeployers;
2001   - }
2002   -
2003   - public AbstractEngineConfiguration setCustomPostDeployers(List<EngineDeployer> customPostDeployers) {
2004   - this.customPostDeployers = customPostDeployers;
2005   - return this;
2006   - }
2007   -
2008   - public boolean isEnableConfiguratorServiceLoader() {
2009   - return enableConfiguratorServiceLoader;
2010   - }
2011   -
2012   - public AbstractEngineConfiguration setEnableConfiguratorServiceLoader(boolean enableConfiguratorServiceLoader) {
2013   - this.enableConfiguratorServiceLoader = enableConfiguratorServiceLoader;
2014   - return this;
2015   - }
2016   -
2017   - public List<EngineConfigurator> getConfigurators() {
2018   - return configurators;
2019   - }
2020   -
2021   - public AbstractEngineConfiguration addConfigurator(EngineConfigurator configurator) {
2022   - if (configurators == null) {
2023   - configurators = new ArrayList<>();
2024   - }
2025   - configurators.add(configurator);
2026   - return this;
2027   - }
2028   -
2029   - /**
2030   - * @return All {@link EngineConfigurator} instances. Will only contain values after init of the engine.
2031   - * Use the {@link #getConfigurators()} or {@link #addConfigurator(EngineConfigurator)} methods otherwise.
2032   - */
2033   - public List<EngineConfigurator> getAllConfigurators() {
2034   - return allConfigurators;
2035   - }
2036   -
2037   - public AbstractEngineConfiguration setConfigurators(List<EngineConfigurator> configurators) {
2038   - this.configurators = configurators;
2039   - return this;
2040   - }
2041   -
2042   - public EngineConfigurator getIdmEngineConfigurator() {
2043   - return idmEngineConfigurator;
2044   - }
2045   -
2046   - public AbstractEngineConfiguration setIdmEngineConfigurator(EngineConfigurator idmEngineConfigurator) {
2047   - this.idmEngineConfigurator = idmEngineConfigurator;
2048   - return this;
2049   - }
2050   -
2051   - public EngineConfigurator getEventRegistryConfigurator() {
2052   - return eventRegistryConfigurator;
2053   - }
2054   -
2055   - public AbstractEngineConfiguration setEventRegistryConfigurator(EngineConfigurator eventRegistryConfigurator) {
2056   - this.eventRegistryConfigurator = eventRegistryConfigurator;
2057   - return this;
2058   - }
2059   -
2060   - public AbstractEngineConfiguration setForceCloseMybatisConnectionPool(boolean forceCloseMybatisConnectionPool) {
2061   - this.forceCloseMybatisConnectionPool = forceCloseMybatisConnectionPool;
2062   - return this;
2063   - }
2064   -
2065   - public boolean isForceCloseMybatisConnectionPool() {
2066   - return forceCloseMybatisConnectionPool;
2067   - }
2068   -}
sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md deleted
1   -防止IDEA将`.`和`/`混为一谈
2 0 \ No newline at end of file
sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database deleted
1   -liquibase.database.core.CockroachDatabase
2   -liquibase.database.core.DB2Database
3   -liquibase.database.core.Db2zDatabase
4   -liquibase.database.core.DerbyDatabase
5   -liquibase.database.core.Firebird3Database
6   -liquibase.database.core.FirebirdDatabase
7   -liquibase.database.core.H2Database
8   -liquibase.database.core.HsqlDatabase
9   -liquibase.database.core.InformixDatabase
10   -liquibase.database.core.Ingres9Database
11   -liquibase.database.core.MSSQLDatabase
12   -liquibase.database.core.MariaDBDatabase
13   -liquibase.database.core.MockDatabase
14   -liquibase.database.core.MySQLDatabase
15   -liquibase.database.core.OracleDatabase
16   -liquibase.database.core.PostgresDatabase
17   -liquibase.database.core.SQLiteDatabase
18   -liquibase.database.core.SybaseASADatabase
19   -liquibase.database.core.SybaseDatabase
20   -liquibase.database.core.DmDatabase
21   -liquibase.database.core.UnsupportedDatabase
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.mybatis.config;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.util.StrUtil;
5 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 5 import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
... ... @@ -23,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
23 22 import org.apache.ibatis.annotations.Mapper;
24 23 import org.apache.ibatis.mapping.ResultMap;
25 24 import org.mybatis.spring.annotation.MapperScan;
  25 +import org.springframework.beans.factory.ObjectProvider;
  26 +import org.springframework.beans.factory.SmartInitializingSingleton;
26 27 import org.springframework.boot.autoconfigure.AutoConfiguration;
27 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
28 29 import org.springframework.context.annotation.Bean;
... ... @@ -30,7 +31,6 @@ import org.springframework.context.annotation.Primary;
30 31 import org.springframework.core.env.ConfigurableEnvironment;
31 32  
32 33 import java.util.ArrayList;
33   -import java.util.List;
34 34 import java.util.concurrent.TimeUnit;
35 35  
36 36 /**
... ... @@ -49,6 +49,8 @@ public class YudaoMybatisAutoConfiguration {
49 49 (cache) -> cache.maximumSize(1024)
50 50 .expireAfterWrite(5, TimeUnit.SECONDS))
51 51 );
  52 + // 特殊:JacksonTypeHandler 先使用 JsonUtils 的 ObjectMapper(支持 LocalDateTime),避免使用 MyBatis Plus 默认的 ObjectMapper
  53 + JacksonTypeHandler.setObjectMapper(JsonUtils.getObjectMapper());
52 54 }
53 55  
54 56 @Bean
... ... @@ -101,15 +103,11 @@ public class YudaoMybatisAutoConfiguration {
101 103 throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType));
102 104 }
103 105  
104   - @Bean // 特殊:返回结果使用 Object 而不用 JacksonTypeHandler 的原因,避免因为 JacksonTypeHandler 被 mybatis 全局使用!
105   - public Object jacksonTypeHandler(List<ObjectMapper> objectMappers) {
106   - // 特殊:设置 JacksonTypeHandler 的 ObjectMapper!
107   - ObjectMapper objectMapper = CollUtil.getFirst(objectMappers);
108   - if (objectMapper == null) {
109   - objectMapper = JsonUtils.getObjectMapper();
110   - }
111   - JacksonTypeHandler.setObjectMapper(objectMapper);
112   - return new JacksonTypeHandler(Object.class);
  106 + @Bean
  107 + public SmartInitializingSingleton jacksonTypeHandlerInitializer(ObjectProvider<ObjectMapper> objectMappers) {
  108 + // 特殊:所有单例初始化后,设置 JacksonTypeHandler 的 ObjectMapper,优先唯一或 @Primary 的 ObjectMapper,否则使用 JsonUtils 的
  109 + // 原因:避免延迟初始化未设置、多个 ObjectMapper 时取到不支持 LocalDateTime 的,且不在创建 SqlSessionFactory 时获取 ObjectMapper。https://t.zsxq.com/KmPNU
  110 + return () -> JacksonTypeHandler.setObjectMapper(objectMappers.getIfUnique(JsonUtils::getObjectMapper));
113 111 }
114 112  
115 113 }
... ...
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
... ... @@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
6 6 import cn.hutool.http.HttpUtil;
7 7 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
8 8 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
  9 +import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils;
9 10 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
10 11 import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
11 12 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
... ... @@ -33,6 +34,8 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
33 34  
34 35 private static final Duration EXPIRATION_DEFAULT = Duration.ofHours(24);
35 36  
  37 + private static final String CONTENT_DISPOSITION_ATTACHMENT = "attachment";
  38 +
36 39 private S3Client client;
37 40 private S3Presigner presigner;
38 41  
... ... @@ -79,6 +82,8 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
79 82 .bucket(config.getBucket())
80 83 .key(path)
81 84 .contentType(type)
  85 + // SVG 等可携带脚本的文件:直链直接打开时按附件下载;页面中作为图片引用时不受影响
  86 + .contentDisposition(FileTypeUtils.isScriptable(type) ? CONTENT_DISPOSITION_ATTACHMENT : null)
82 87 .contentLength((long) content.length)
83 88 .build();
84 89 // 上传文件
... ... @@ -133,9 +138,14 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
133 138 // 2.2 情况二:私有访问:生成 GET 预签名 URL
134 139 String finalPath = path;
135 140 Duration expiration = expirationSeconds != null ? Duration.ofSeconds(expirationSeconds) : EXPIRATION_DEFAULT;
  141 + // 新签发的私有 URL:对象 key 扩展名为 SVG 等可携带脚本的类型时,要求按附件下载
  142 + // 注意:不覆盖公开访问、已签发的 URL,以及扩展名与 Content-Type 不一致的直传对象
  143 + String contentDisposition = FileTypeUtils.isScriptable(FileTypeUtils.getMineType(finalPath))
  144 + ? CONTENT_DISPOSITION_ATTACHMENT : null;
136 145 URL signedUrl = presigner.presignGetObject(GetObjectPresignRequest.builder()
137 146 .signatureDuration(expiration)
138   - .getObjectRequest(b -> b.bucket(config.getBucket()).key(finalPath)).build())
  147 + .getObjectRequest(b -> b.bucket(config.getBucket()).key(finalPath)
  148 + .responseContentDisposition(contentDisposition)).build())
139 149 .url();
140 150 return signedUrl.toString();
141 151 }
... ...
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java
... ... @@ -81,13 +81,24 @@ public class FileTypeUtils {
81 81 public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
82 82 // 设置 header 和 contentType
83 83 String mineType = getMineType(content, filename);
  84 + // 文件名可能伪装,按内容识别为 SVG、HTML、XML 等可携带脚本的类型时,以内容为准
  85 + String contentMineType = getMineType(content);
  86 + if (!isScriptable(mineType) && isScriptable(contentMineType)) {
  87 + mineType = contentMineType;
  88 + }
84 89 response.setContentType(mineType);
  90 + response.setHeader("X-Content-Type-Options", "nosniff");
85 91 // 设置内容显示、下载文件名:https://www.cnblogs.com/wq-9/articles/12165056.html
86   - if (isImage(mineType)) {
  92 + if (isImage(mineType) && !isScriptable(mineType)) {
87 93 // 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论
88 94 response.setHeader("Content-Disposition", buildContentDisposition("inline", filename));
89 95 } else {
  96 + // SVG 等可携带脚本的文件:直接打开时按附件下载;保留原类型,页面中作为图片引用时仍可显示
90 97 response.setHeader("Content-Disposition", buildContentDisposition("attachment", filename));
  98 + // 被 object、embed 等嵌入时,按沙箱执行,禁止脚本、隔离来源
  99 + if (isScriptable(mineType)) {
  100 + response.setHeader("Content-Security-Policy", "sandbox");
  101 + }
91 102 }
92 103 // 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
93 104 if (StrUtil.containsIgnoreCase(mineType, "video")) {
... ... @@ -131,4 +142,16 @@ public class FileTypeUtils {
131 142 return StrUtil.startWith(mineType, "image/");
132 143 }
133 144  
  145 + /**
  146 + * 判断是否是可携带脚本的类型,例如说 SVG、HTML、XHTML、XML,同源直接打开时会执行其中的脚本
  147 + *
  148 + * 注意:Office 的 openxmlformats 类型以 document、sheet 等结尾,不属于此类
  149 + *
  150 + * @param mineType 类型
  151 + * @return 是否可携带脚本
  152 + */
  153 + public static boolean isScriptable(String mineType) {
  154 + return StrUtil.containsAnyIgnoreCase(mineType, "svg", "html") || StrUtil.endWithIgnoreCase(mineType, "xml");
  155 + }
  156 +
134 157 }
... ...
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/s3/S3FileClientTest.java
... ... @@ -2,16 +2,27 @@ package cn.iocoder.yudao.module.infra.framework.file.core.s3;
2 2  
3 3 import cn.hutool.core.io.resource.ResourceUtil;
4 4 import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.ReflectUtil;
  6 +import cn.hutool.core.util.StrUtil;
5 7 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
6 8 import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClient;
7 9 import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.S3FileClientConfig;
8 10 import jakarta.validation.Validation;
9 11 import org.junit.jupiter.api.Disabled;
10 12 import org.junit.jupiter.api.Test;
  13 +import org.mockito.ArgumentCaptor;
  14 +import software.amazon.awssdk.core.sync.RequestBody;
  15 +import software.amazon.awssdk.services.s3.S3Client;
  16 +import software.amazon.awssdk.services.s3.model.PutObjectRequest;
11 17  
12 18 import static org.junit.jupiter.api.Assertions.assertEquals;
13 19 import static org.junit.jupiter.api.Assertions.assertFalse;
  20 +import static org.junit.jupiter.api.Assertions.assertNull;
14 21 import static org.junit.jupiter.api.Assertions.assertTrue;
  22 +import static org.mockito.ArgumentMatchers.any;
  23 +import static org.mockito.Mockito.mock;
  24 +import static org.mockito.Mockito.times;
  25 +import static org.mockito.Mockito.verify;
15 26  
16 27 @SuppressWarnings("resource")
17 28 public class S3FileClientTest {
... ... @@ -67,6 +78,68 @@ public class S3FileClientTest {
67 78 assertTrue(result.contains("/yudao/avatar/%E4%B8%AD%E6%96%87%20100%25"));
68 79 assertTrue(result.contains("%E6%96%87%E4%BB%B6.jpg"));
69 80 assertFalse(result.contains("%25E4%25B8%25AD"));
  81 + assertFalse(result.contains("response-content-disposition"));
  82 + }
  83 +
  84 + @Test
  85 + public void testPresignUrl_privateAccess_scriptableAttachment() {
  86 + // 准备参数
  87 + S3FileClientConfig config = new S3FileClientConfig();
  88 + config.setAccessKey("admin");
  89 + config.setAccessSecret("password");
  90 + config.setBucket("yudao");
  91 + config.setDomain("http://127.0.0.1:9000/yudao");
  92 + config.setEndpoint("http://127.0.0.1:9000");
  93 + config.setEnablePathStyleAccess(true);
  94 + config.setEnablePublicAccess(false);
  95 + S3FileClient client = new S3FileClient(0L, config);
  96 + client.init();
  97 +
  98 + // 调用:新上传的 SVG、已保存的历史签名 URL 重新签名
  99 + String result = client.presignGetUrl("avatar/图标.svg", 300);
  100 + String historyResult = client.presignGetUrl(
  101 + "http://127.0.0.1:9000/yudao/avatar/%E5%9B%BE%E6%A0%87.svg?X-Amz-Signature=old", 300);
  102 + String putResult = client.presignPutUrl("avatar/图标.svg");
  103 +
  104 + // 断言:GET 签名要求按附件下载;PUT 签名不额外约束请求头,兼容前端直传
  105 + assertTrue(result.contains("/yudao/avatar/%E5%9B%BE%E6%A0%87.svg"));
  106 + assertTrue(result.contains("response-content-disposition=attachment"));
  107 + assertTrue(historyResult.contains("/yudao/avatar/%E5%9B%BE%E6%A0%87.svg"));
  108 + assertTrue(historyResult.contains("response-content-disposition=attachment"));
  109 + assertFalse(historyResult.contains("X-Amz-Signature=old"));
  110 + assertFalse(StrUtil.containsIgnoreCase(putResult, "content-disposition"));
  111 + }
  112 +
  113 + @Test
  114 + public void testUpload_scriptableContentDisposition() {
  115 + // 准备参数
  116 + S3FileClientConfig config = new S3FileClientConfig();
  117 + config.setAccessKey("admin");
  118 + config.setAccessSecret("password");
  119 + config.setBucket("yudao");
  120 + config.setDomain("http://127.0.0.1:9000/yudao");
  121 + config.setEndpoint("http://127.0.0.1:9000");
  122 + config.setEnablePathStyleAccess(true);
  123 + config.setEnablePublicAccess(true);
  124 + S3FileClient client = new S3FileClient(0L, config);
  125 + client.init();
  126 + // mock S3 客户端
  127 + S3Client s3Client = mock(S3Client.class);
  128 + ReflectUtil.setFieldValue(client, "client", s3Client);
  129 +
  130 + // 调用
  131 + client.upload(new byte[]{1}, "avatar/图标.svg", "image/svg+xml");
  132 + client.upload(new byte[]{1}, "avatar/页面.html", "text/html");
  133 + client.upload(new byte[]{1}, "avatar/头像.jpg", "image/jpeg");
  134 +
  135 + // 断言:可携带脚本的对象按附件下载,普通图片不设置
  136 + ArgumentCaptor<PutObjectRequest> requestCaptor = ArgumentCaptor.forClass(PutObjectRequest.class);
  137 + verify(s3Client, times(3)).putObject(requestCaptor.capture(), any(RequestBody.class));
  138 + assertEquals("image/svg+xml", requestCaptor.getAllValues().get(0).contentType());
  139 + assertEquals("attachment", requestCaptor.getAllValues().get(0).contentDisposition());
  140 + assertEquals("attachment", requestCaptor.getAllValues().get(1).contentDisposition());
  141 + assertEquals("image/jpeg", requestCaptor.getAllValues().get(2).contentType());
  142 + assertNull(requestCaptor.getAllValues().get(2).contentDisposition());
70 143 }
71 144  
72 145 @Test
... ...
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtilsTest.java
1 1 package cn.iocoder.yudao.module.infra.framework.file.core.utils;
2 2  
  3 +import cn.hutool.core.io.resource.ResourceUtil;
3 4 import org.junit.jupiter.api.Test;
4 5 import org.springframework.mock.web.MockHttpServletResponse;
5 6  
6 7 import java.nio.charset.StandardCharsets;
7 8  
8   -import static org.junit.jupiter.api.Assertions.assertArrayEquals;
9   -import static org.junit.jupiter.api.Assertions.assertEquals;
  9 +import static org.junit.jupiter.api.Assertions.*;
10 10  
11 11 /**
12 12 * {@link FileTypeUtils} 的单元测试
... ... @@ -26,7 +26,102 @@ public class FileTypeUtilsTest {
26 26 assertEquals("attachment;filename=\"__ 100%+__.txt\";"
27 27 + "filename*=UTF-8''%E4%B8%AD%E6%96%87%20100%25+%E6%96%87%E4%BB%B6.txt",
28 28 response.getHeader("Content-Disposition"));
  29 + assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
  30 + assertNull(response.getHeader("Content-Security-Policy"));
29 31 assertArrayEquals(content, response.getContentAsByteArray());
30 32 }
31 33  
  34 + @Test
  35 + public void testWriteAttachment_imageInline() throws Exception {
  36 + // 准备参数
  37 + MockHttpServletResponse response = new MockHttpServletResponse();
  38 + byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
  39 +
  40 + // 调用
  41 + FileTypeUtils.writeAttachment(response, "头像.jpg", content);
  42 +
  43 + // 断言:普通位图保持 inline,可直接预览
  44 + assertEquals("image/jpeg", response.getContentType());
  45 + assertEquals("inline;filename=\"__.jpg\";filename*=UTF-8''%E5%A4%B4%E5%83%8F.jpg",
  46 + response.getHeader("Content-Disposition"));
  47 + assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
  48 + assertNull(response.getHeader("Content-Security-Policy"));
  49 + assertArrayEquals(content, response.getContentAsByteArray());
  50 + }
  51 +
  52 + @Test
  53 + public void testWriteAttachment_scriptableAttachment() throws Exception {
  54 + // 历史已上传的 SVG、HTML、XML 文件,没有文件记录时按路径中的文件名下载
  55 + for (String[] document : new String[][]{
  56 + {"图标.svg", "<svg xmlns=\"http://www.w3.org/2000/svg\"><script>alert(1)</script></svg>",
  57 + "image/svg+xml", "attachment;filename=\"__.svg\";filename*=UTF-8''%E5%9B%BE%E6%A0%87.svg"},
  58 + {"页面.html", "<html><script>alert(1)</script></html>",
  59 + "text/html", "attachment;filename=\"__.html\";filename*=UTF-8''%E9%A1%B5%E9%9D%A2.html"},
  60 + {"数据.xml", "<?xml version=\"1.0\"?><root>test</root>",
  61 + "application/xml", "attachment;filename=\"__.xml\";filename*=UTF-8''%E6%95%B0%E6%8D%AE.xml"}}) {
  62 + // 准备参数
  63 + MockHttpServletResponse response = new MockHttpServletResponse();
  64 + byte[] content = document[1].getBytes(StandardCharsets.UTF_8);
  65 +
  66 + // 调用
  67 + FileTypeUtils.writeAttachment(response, document[0], content);
  68 +
  69 + // 断言:保留原类型,直接打开时按附件下载,嵌入时按沙箱执行
  70 + assertEquals(document[2], response.getContentType());
  71 + assertEquals(document[3], response.getHeader("Content-Disposition"));
  72 + assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
  73 + assertEquals("sandbox", response.getHeader("Content-Security-Policy"));
  74 + assertArrayEquals(content, response.getContentAsByteArray());
  75 + }
  76 + }
  77 +
  78 + @Test
  79 + public void testWriteAttachment_fakeImageNameDetectContent() throws Exception {
  80 + // 准备参数:SVG 内容伪装成 png 文件名
  81 + MockHttpServletResponse response = new MockHttpServletResponse();
  82 + byte[] content = "<svg xmlns=\"http://www.w3.org/2000/svg\"><script>alert(1)</script></svg>"
  83 + .getBytes(StandardCharsets.UTF_8);
  84 +
  85 + // 调用
  86 + FileTypeUtils.writeAttachment(response, "图标.png", content);
  87 +
  88 + // 断言:以内容识别的 SVG 类型为准,按附件下载
  89 + assertEquals("image/svg+xml", response.getContentType());
  90 + assertEquals("attachment;filename=\"__.png\";filename*=UTF-8''%E5%9B%BE%E6%A0%87.png",
  91 + response.getHeader("Content-Disposition"));
  92 + assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
  93 + assertEquals("sandbox", response.getHeader("Content-Security-Policy"));
  94 + }
  95 +
  96 + @Test
  97 + public void testWriteAttachment_imageNameHintDetectContent() throws Exception {
  98 + // 准备参数:XML 内容,文件名提示会把类型识别为图片(image/vnd.adobe.premiere)
  99 + MockHttpServletResponse response = new MockHttpServletResponse();
  100 + byte[] content = "<?xml version=\"1.0\"?><root>test</root>".getBytes(StandardCharsets.UTF_8);
  101 +
  102 + // 调用
  103 + FileTypeUtils.writeAttachment(response, "test.ppj", content);
  104 +
  105 + // 断言:仅按内容识别为 XML 时以内容为准,不按图片 inline
  106 + assertEquals("application/xml", response.getContentType());
  107 + assertEquals("attachment;filename=\"test.ppj\";filename*=UTF-8''test.ppj",
  108 + response.getHeader("Content-Disposition"));
  109 + assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
  110 + assertEquals("sandbox", response.getHeader("Content-Security-Policy"));
  111 + assertArrayEquals(content, response.getContentAsByteArray());
  112 + }
  113 +
  114 + @Test
  115 + public void testIsScriptable() {
  116 + assertTrue(FileTypeUtils.isScriptable("image/svg+xml"));
  117 + assertTrue(FileTypeUtils.isScriptable("text/html"));
  118 + assertTrue(FileTypeUtils.isScriptable("application/xhtml+xml"));
  119 + assertTrue(FileTypeUtils.isScriptable("application/xml"));
  120 + assertTrue(FileTypeUtils.isScriptable("text/xml"));
  121 + assertFalse(FileTypeUtils.isScriptable("image/png"));
  122 + assertFalse(FileTypeUtils.isScriptable("application/pdf"));
  123 + assertFalse(FileTypeUtils.isScriptable("application/vnd.openxmlformats-officedocument.wordprocessingml.document"));
  124 + assertFalse(FileTypeUtils.isScriptable(null));
  125 + }
  126 +
32 127 }
... ...
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
... ... @@ -252,6 +252,28 @@ public class FileServiceImplTest extends BaseDbUnitTest {
252 252 }
253 253  
254 254 @Test
  255 + public void testCreateFile_success_fakeExtensionDetectContent() throws Exception {
  256 + for (String name : new String[]{"图标.xml", "图标.txt"}) {
  257 + // 准备参数:SVG 内容伪装成其他扩展名
  258 + byte[] content = "<svg xmlns=\"http://www.w3.org/2000/svg\"><script>alert(1)</script></svg>"
  259 + .getBytes(StandardCharsets.UTF_8);
  260 + // mock 方法
  261 + FileClient client = mock(FileClient.class);
  262 + when(fileConfigService.getMasterFileClient()).thenReturn(client);
  263 + String url = randomString();
  264 + when(client.upload(same(content), anyString(), eq("image/svg+xml"))).thenReturn(url);
  265 +
  266 + // 调用
  267 + String result = fileService.createFile(content, name, null, "text/plain");
  268 + // 断言:以内容识别的类型上传,文件存储器据此按附件下载
  269 + assertEquals(url, result);
  270 + FileDO file = fileMapper.selectOne(FileDO::getUrl, url);
  271 + assertEquals(name, file.getName());
  272 + assertEquals("image/svg+xml", file.getType());
  273 + }
  274 + }
  275 +
  276 + @Test
255 277 public void testCreateFile_success_opus() throws Exception {
256 278 // 准备参数
257 279 byte[] content = new byte[64];
... ...
yudao-module-oa/src/test/java/cn/iocoder/yudao/module/oa/service/reimbursement/OaReimbursementServiceImplTest.java
1 1 package cn.iocoder.yudao.module.oa.service.reimbursement;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.date.LocalDateTimeUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.framework.jackson.config.YudaoJacksonAutoConfiguration;
6 7 import cn.iocoder.yudao.framework.security.core.LoginUser;
... ... @@ -14,6 +15,8 @@ import cn.iocoder.yudao.module.oa.controller.admin.reimbursement.vo.OaReimbursem
14 15 import cn.iocoder.yudao.module.oa.dal.dataobject.reimbursement.OaReimbursementDO;
15 16 import cn.iocoder.yudao.module.oa.dal.mysql.reimbursement.OaReimbursementMapper;
16 17 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  18 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  19 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
17 20 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
18 21 import com.fasterxml.jackson.databind.ObjectMapper;
19 22 import jakarta.annotation.Resource;
... ... @@ -30,6 +33,7 @@ import java.math.BigDecimal;
30 33 import java.time.LocalDateTime;
31 34 import java.util.Arrays;
32 35 import java.util.Collections;
  36 +import java.util.List;
33 37  
34 38 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
35 39 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
... ... @@ -61,8 +65,6 @@ public class OaReimbursementServiceImplTest extends BaseDbUnitTest {
61 65  
62 66 @BeforeEach
63 67 public void before() {
64   - // JSON 处理器为静态共享配置,每个用例恢复当前上下文的生产日期序列化配置
65   - JacksonTypeHandler.setObjectMapper(objectMapper);
66 68 SecurityFrameworkUtils.setLoginUser(new LoginUser().setId(10L).setUserType(2), new MockHttpServletRequest());
67 69 }
68 70  
... ... @@ -126,6 +128,7 @@ public class OaReimbursementServiceImplTest extends BaseDbUnitTest {
126 128 assertEquals(new BigDecimal("12.34"), application.getTotalPrice());
127 129 assertEquals(2, application.getInvoiceCount());
128 130 assertEquals(1, CollUtil.getFirst(application.getItems()).getExpenseType());
  131 + assertEquals(LocalDateTime.of(2026, 9, 13, 8, 0), CollUtil.getFirst(application.getItems()).getExpenseTime());
129 132 verify(processInstanceApi).createProcessInstance(eq(10L), argThat(dto ->
130 133 "oa_reimbursement".equals(dto.getProcessDefinitionKey()) && id.toString().equals(dto.getBusinessKey())
131 134 && !dto.getVariables().containsKey("title")));
... ... @@ -214,6 +217,42 @@ public class OaReimbursementServiceImplTest extends BaseDbUnitTest {
214 217 }
215 218  
216 219 @Test
  220 + public void testGetReimbursementAndPage_itemsExpenseTime() {
  221 + // mock 数据
  222 + Long id = reimbursementService.createReimbursement(randomReimbursementSaveReqVO());
  223 + LocalDateTime expenseTime = LocalDateTime.of(2026, 9, 13, 8, 0);
  224 +
  225 + // 调用
  226 + OaReimbursementDO reimbursement = reimbursementService.getReimbursement(id);
  227 + PageResult<OaReimbursementDO> page = reimbursementService.getReimbursementPage(10L, new OaReimbursementPageReqVO());
  228 +
  229 + // 断言:使用 Spring 主 ObjectMapper,详情、列表读取明细时间,库中保持毫秒时间戳
  230 + assertSame(objectMapper, JacksonTypeHandler.getObjectMapper());
  231 + assertEquals(expenseTime, CollUtil.getFirst(reimbursement.getItems()).getExpenseTime());
  232 + assertEquals(1L, page.getTotal());
  233 + assertEquals(expenseTime, CollUtil.getFirst(CollUtil.getFirst(page.getList()).getItems()).getExpenseTime());
  234 + List<String> items = reimbursementMapper.selectObjs(new LambdaQueryWrapper<OaReimbursementDO>()
  235 + .select(OaReimbursementDO::getItems).eq(OaReimbursementDO::getId, id));
  236 + assertTrue(CollUtil.getFirst(items).contains("\"expenseTime\":" + LocalDateTimeUtil.toEpochMilli(expenseTime)));
  237 + }
  238 +
  239 + @Test
  240 + public void testGetReimbursementPage_itemsExpenseTimeString() {
  241 + // mock 数据:库中明细时间为可读字符串
  242 + Long id = reimbursementService.createReimbursement(randomReimbursementSaveReqVO());
  243 + reimbursementMapper.update(new LambdaUpdateWrapper<OaReimbursementDO>().eq(OaReimbursementDO::getId, id)
  244 + .setSql("items = {0}", "[{\"expenseTime\":\"2026-09-13 08:00:00\",\"expenseType\":1,\"price\":12.34}]"));
  245 +
  246 + // 调用
  247 + PageResult<OaReimbursementDO> page = reimbursementService.getReimbursementPage(10L, new OaReimbursementPageReqVO());
  248 +
  249 + // 断言
  250 + assertEquals(1L, page.getTotal());
  251 + assertEquals(LocalDateTime.of(2026, 9, 13, 8, 0),
  252 + CollUtil.getFirst(CollUtil.getFirst(page.getList()).getItems()).getExpenseTime());
  253 + }
  254 +
  255 + @Test
217 256 public void testUpdateReimbursementStatus_successAndRepeated() {
218 257 // mock 数据
219 258 OaReimbursementDO application = randomReimbursementDO();
... ...
yudao-module-oa/src/test/java/cn/iocoder/yudao/module/oa/service/travel/OaTravelReimbursementServiceImplTest.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.oa.service.travel;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 6 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
6 7 import cn.iocoder.yudao.framework.security.core.LoginUser;
7 8 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
... ... @@ -16,6 +17,7 @@ import cn.iocoder.yudao.module.oa.dal.redis.no.OaNoRedisDAO;
16 17 import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
17 18 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
18 19 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  20 +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
19 21 import jakarta.annotation.Resource;
20 22 import jakarta.validation.ConstraintViolationException;
21 23 import org.junit.jupiter.api.AfterEach;
... ... @@ -150,6 +152,7 @@ public class OaTravelReimbursementServiceImplTest extends BaseDbUnitTest {
150 152 OaTravelReimbursementDO record = travelReimbursementMapper.selectById(id);
151 153 assertEquals(1, record.getDays());
152 154 assertEquals(reqVO.getItems().size(), record.getItems().size());
  155 + assertEquals(LocalDateTime.of(2026, 9, 14, 10, 0), CollUtil.getFirst(record.getItems()).getExpenseTime());
153 156 assertEquals(reqVO.getFileUrls(), record.getFileUrls());
154 157 assertEquals(new BigDecimal("0.00"), record.getTotalPrice());
155 158 assertEquals("TRAVEL-NEW", record.getNo());
... ... @@ -423,6 +426,25 @@ public class OaTravelReimbursementServiceImplTest extends BaseDbUnitTest {
423 426 }
424 427  
425 428 @Test
  429 + public void testGetTravelReimbursementAndPage_itemsExpenseTime() {
  430 + // mock 数据
  431 + OaTravelReimbursementDO record = randomTravelReimbursementDO();
  432 + travelReimbursementMapper.insert(record);
  433 + LocalDateTime expenseTime = LocalDateTime.of(2026, 9, 14, 10, 0);
  434 +
  435 + // 调用
  436 + OaTravelReimbursementDO detail = travelReimbursementService.getTravelReimbursement(record.getId());
  437 + PageResult<OaTravelReimbursementDO> page = travelReimbursementService.getTravelReimbursementPage(10L,
  438 + new OaTravelReimbursementPageReqVO());
  439 +
  440 + // 断言:未声明 ObjectMapper Bean 时使用 JsonUtils 的 ObjectMapper,详情、列表读取明细时间
  441 + assertSame(JsonUtils.getObjectMapper(), JacksonTypeHandler.getObjectMapper());
  442 + assertEquals(expenseTime, CollUtil.getFirst(detail.getItems()).getExpenseTime());
  443 + assertEquals(1L, page.getTotal());
  444 + assertEquals(expenseTime, CollUtil.getFirst(CollUtil.getFirst(page.getList()).getItems()).getExpenseTime());
  445 + }
  446 +
  447 + @Test
426 448 public void testUpdateTravelReimbursementStatus_notExists() {
427 449  
428 450 // 调用,并断言异常
... ... @@ -593,7 +615,8 @@ public class OaTravelReimbursementServiceImplTest extends BaseDbUnitTest {
593 615 .setStartTime(LocalDateTime.of(2026, 9, 14, 0, 0)).setEndTime(LocalDateTime.of(2026, 9, 15, 0, 0))
594 616 .setStatus(-1).setProcessInstanceId(null).setPayStatus(false)
595 617 .setTravelApplyId(null).setTotalPrice(BigDecimal.ZERO)
596   - .setItems(Collections.singletonList(new OaTravelReimbursementDO.Item().setPrice(BigDecimal.ZERO)))
  618 + .setItems(Collections.singletonList(new OaTravelReimbursementDO.Item().setPrice(BigDecimal.ZERO)
  619 + .setExpenseTime(LocalDateTime.of(2026, 9, 14, 10, 0))))
597 620 .setFileUrls(Collections.singletonList("https://example.com/receipt.pdf")));
598 621 record.setCreator("10");
599 622 return record;
... ...