Selaa lähdekoodia

!723 feat: 达梦flowable 6.8适配
Merge pull request !723 from dhb52/master

芋道源码 1 vuosi sitten
vanhempi
commit
d07869f461

+ 28 - 0
sql/dm/flowable-6.8.md

@@ -0,0 +1,28 @@
+# 达梦 flowable 适配
+
+参考文档: [《Flowable6.8(6.x 版本通用)整合集成达梦 8 数据库(DM8)详解,解决自动生成表时 dmn 相关表语法报错问题》](https://blog.csdn.net/TangBoBoa/article/details/130392495)
+
+## 1. 覆盖 flowable,liquibase 相关代码
+
+把`flowable-patch/src`下的文件按文件结果添加到`yudao-server`或者`yudao-module-bpm-biz`项目对应目录中,甚至你可以做个独立模块。
+
+## 2. 修改相关 DAO
+
+例如`cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO`
+
+```diff
+- @TableField("`type`")
+private String type;
+```
+
+## 3. 关于`flowable.database-schema-update`配置
+
+首次运行,修改`flowable.database-schema-update=true`,系统会自动建表,第二次运行需要修改为`false`。
+
+## 4. TODO
+
+配置`flowable.database-schema-update=true`第二次运行失败,报错
+
+```text
+Object [FLW_EV_DATABASECHANGELOG] already exists
+```

+ 598 - 0
sql/dm/flowable-patch/src/main/java/liquibase/database/core/DmDatabase.java

@@ -0,0 +1,598 @@
+package liquibase.database.core;
+
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import liquibase.CatalogAndSchema;
+import liquibase.Scope;
+import liquibase.database.AbstractJdbcDatabase;
+import liquibase.database.DatabaseConnection;
+import liquibase.database.OfflineConnection;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.DatabaseException;
+import liquibase.exception.UnexpectedLiquibaseException;
+import liquibase.exception.ValidationErrors;
+import liquibase.executor.ExecutorService;
+import liquibase.statement.DatabaseFunction;
+import liquibase.statement.SequenceCurrentValueFunction;
+import liquibase.statement.SequenceNextValueFunction;
+import liquibase.statement.core.RawCallStatement;
+import liquibase.statement.core.RawSqlStatement;
+import liquibase.structure.DatabaseObject;
+import liquibase.structure.core.Catalog;
+import liquibase.structure.core.Index;
+import liquibase.structure.core.PrimaryKey;
+import liquibase.structure.core.Schema;
+import liquibase.util.JdbcUtils;
+import liquibase.util.StringUtil;
+
+public class DmDatabase extends AbstractJdbcDatabase {
+    private static final String PRODUCT_NAME = "DM DBMS";
+
+    @Override
+    protected String getDefaultDatabaseProductName() {
+        return PRODUCT_NAME;
+    }
+
+    /**
+     * Is this AbstractDatabase subclass the correct one to use for the given connection.
+     *
+     * @param conn
+     */
+    @Override
+    public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
+        return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
+    }
+
+    /**
+     * If this database understands the given url, return the default driver class name.  Otherwise return null.
+     *
+     * @param url
+     */
+    @Override
+    public String getDefaultDriver(String url) {
+        if(url.startsWith("jdbc:dm")) {
+            return "dm.jdbc.driver.DmDriver";
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns an all-lower-case short name of the product.  Used for end-user selecting of database type
+     * such as the DBMS precondition.
+     */
+    @Override
+    public String getShortName() {
+        return "dm";
+    }
+
+    @Override
+    public Integer getDefaultPort() {
+        return 5236;
+    }
+
+    /**
+     * Returns whether this database support initially deferrable columns.
+     */
+    @Override
+    public boolean supportsInitiallyDeferrableColumns() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsTablespaces() {
+        return true;
+    }
+
+    @Override
+    public int getPriority() {
+        return PRIORITY_DEFAULT;
+    }
+
+    private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
+
+    protected final int SHORT_IDENTIFIERS_LENGTH = 30;
+    protected final int LONG_IDENTIFIERS_LEGNTH = 128;
+    public static final int ORACLE_12C_MAJOR_VERSION = 12;
+
+    private Set<String> reservedWords = new HashSet<>();
+    private Set<String> userDefinedTypes;
+    private Map<String, String> savedSessionNlsSettings;
+
+    private Boolean canAccessDbaRecycleBin;
+    private Integer databaseMajorVersion;
+    private Integer databaseMinorVersion;
+
+    /**
+     * Default constructor for an object that represents the Oracle Database DBMS.
+     */
+    public DmDatabase() {
+        super.unquotedObjectsAreUppercased = true;
+        //noinspection HardCodedStringLiteral
+        super.setCurrentDateTimeFunction("SYSTIMESTAMP");
+        // Setting list of Oracle's native functions
+        //noinspection HardCodedStringLiteral
+        dateFunctions.add(new DatabaseFunction("SYSDATE"));
+        //noinspection HardCodedStringLiteral
+        dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
+        //noinspection HardCodedStringLiteral
+        dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
+        //noinspection HardCodedStringLiteral
+        super.sequenceNextValueFunction = "%s.nextval";
+        //noinspection HardCodedStringLiteral
+        super.sequenceCurrentValueFunction = "%s.currval";
+    }
+
+    private void tryProxySession(final String url, final Connection con) {
+        Matcher m = PROXY_USER.matcher(url);
+        if (m.matches()) {
+            Properties props = new Properties();
+            props.put("PROXY_USER_NAME", m.group(1));
+            try {
+                Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
+                method.setAccessible(true);
+                method.invoke(con, 1, props);
+            } catch (Exception e) {
+                Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
+            }
+        }
+    }
+
+    @Override
+    public int getDatabaseMajorVersion() throws DatabaseException {
+        if (databaseMajorVersion == null) {
+            return super.getDatabaseMajorVersion();
+        } else {
+            return databaseMajorVersion;
+        }
+    }
+
+    @Override
+    public int getDatabaseMinorVersion() throws DatabaseException {
+        if (databaseMinorVersion == null) {
+            return super.getDatabaseMinorVersion();
+        } else {
+            return databaseMinorVersion;
+        }
+    }
+
+    @Override
+    public String getJdbcCatalogName(CatalogAndSchema schema) {
+        return null;
+    }
+
+    @Override
+    public String getJdbcSchemaName(CatalogAndSchema schema) {
+        return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
+    }
+
+    @Override
+    protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
+        if (StringUtil.isEmpty(generationType)) {
+            return super.getAutoIncrementClause();
+        }
+
+        String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
+        String generationStrategy = generationType;
+        if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
+            generationStrategy += " ON NULL";
+        }
+        return String.format(autoIncrementClause, generationStrategy);
+    }
+
+    @Override
+    public String generatePrimaryKeyName(String tableName) {
+        if (tableName.length() > 27) {
+            //noinspection HardCodedStringLiteral
+            return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
+        } else {
+            //noinspection HardCodedStringLiteral
+            return "PK_" + tableName.toUpperCase(Locale.US);
+        }
+    }
+
+    @Override
+    public boolean isReservedWord(String objectName) {
+        return reservedWords.contains(objectName.toUpperCase());
+    }
+
+    @Override
+    public boolean supportsSequences() {
+        return true;
+    }
+
+    /**
+     * Oracle supports catalogs in liquibase terms
+     *
+     * @return false
+     */
+    @Override
+    public boolean supportsSchemas() {
+        return false;
+    }
+
+    @Override
+    protected String getConnectionCatalogName() throws DatabaseException {
+        if (getConnection() instanceof OfflineConnection) {
+            return getConnection().getCatalog();
+        }
+        try {
+            //noinspection HardCodedStringLiteral
+            return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
+        } catch (Exception e) {
+            //noinspection HardCodedStringLiteral
+            Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
+        }
+        return null;
+    }
+
+    @Override
+    public String getDefaultCatalogName() {//NOPMD
+        return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
+    }
+
+    /**
+     * <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
+     *
+     * <p>Convert an ISO8601 date string to one of the following results:
+     * to_date('1995-05-23', 'YYYY-MM-DD')
+     * to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
+     * <p>
+     * Implementation restriction:<br>
+     * Currently, only the following subsets of ISO8601 are supported:<br>
+     * <ul>
+     * <li>YYYY-MM-DD</li>
+     * <li>YYYY-MM-DDThh:mm:ss</li>
+     * </ul>
+     */
+    @Override
+    public String getDateLiteral(String isoDate) {
+        String normalLiteral = super.getDateLiteral(isoDate);
+
+        if (isDateOnly(isoDate)) {
+            return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
+        } else if (isTimeOnly(isoDate)) {
+            return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
+        } else if (isTimestamp(isoDate)) {
+            return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
+        } else if (isDateTime(isoDate)) {
+            int seppos = normalLiteral.lastIndexOf('.');
+            if (seppos != -1) {
+                normalLiteral = normalLiteral.substring(0, seppos) + "'";
+            }
+            return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
+        }
+        return "UNSUPPORTED:" + isoDate;
+    }
+
+    @Override
+    public boolean isSystemObject(DatabaseObject example) {
+        if (example == null) {
+            return false;
+        }
+
+        if (this.isLiquibaseObject(example)) {
+            return false;
+        }
+
+        if (example instanceof Schema) {
+            //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
+            if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
+                return true;
+            }
+            //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
+            if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
+                return true;
+            }
+        } else if (isSystemObject(example.getSchema())) {
+            return true;
+        }
+        if (example instanceof Catalog) {
+            //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
+            if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
+                return true;
+            }
+        } else if (example.getName() != null) {
+            //noinspection HardCodedStringLiteral
+            if (example.getName().startsWith("BIN$")) { //oracle deleted table
+                boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
+                if (!filteredInOriginalQuery) {
+                    filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
+                }
+
+                if (filteredInOriginalQuery) {
+                    return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
+                            liquibase.statement.UniqueConstraint));
+                } else {
+                    return true;
+                }
+            } else //noinspection HardCodedStringLiteral
+                if (example.getName().startsWith("AQ$")) { //oracle AQ tables
+                    return true;
+                } else //noinspection HardCodedStringLiteral
+                    if (example.getName().startsWith("DR$")) { //oracle index tables
+                        return true;
+                    } else //noinspection HardCodedStringLiteral
+                        if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
+                            return true;
+                        } else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
+                            if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
+                                // CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
+                                return true;
+                            } else //noinspection HardCodedStringLiteral
+                                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.
+                                    return true;
+                                } else //noinspection HardCodedStringLiteral
+                                    if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
+                                        return true;
+                                    } else //noinspection HardCodedStringLiteral
+                                        if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
+                                            return true;
+                                        } else //noinspection HardCodedStringLiteral
+                                            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.
+                                                return true;
+                                            } else //noinspection HardCodedStringLiteral
+                                                if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
+                                                    return true;
+                                                } else //noinspection HardCodedStringLiteral
+                                                    if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
+                                                        return true;
+                                                    } else //noinspection HardCodedStringLiteral
+                                                        if (example.getName().startsWith("USLOG$")) { //for update materialized view
+                                                            return true;
+                                                        } else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
+                                                            return true;
+                                                        }
+        }
+
+        return super.isSystemObject(example);
+    }
+
+    @Override
+    public boolean supportsAutoIncrement() {
+        // Oracle supports Identity beginning with version 12c
+        boolean isAutoIncrementSupported = false;
+
+        try {
+            if (getDatabaseMajorVersion() >= 12) {
+                isAutoIncrementSupported = true;
+            }
+
+            // Returning true will generate create table command with 'IDENTITY' clause, example:
+            // 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));
+
+            // While returning false will continue to generate create table command without 'IDENTITY' clause, example:
+            // CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
+
+        } catch (DatabaseException ex) {
+            isAutoIncrementSupported = false;
+        }
+
+        return isAutoIncrementSupported;
+    }
+
+
+//    public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
+//        Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
+//
+//        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"));
+//
+//        UniqueConstraint constraint = null;
+//        for (Map map : maps) {
+//            if (constraint == null || !constraint.getName().equals(constraint.getName())) {
+//                returnSet.add(constraint);
+//                Table table = new Table((String) map.get("TABLE_NAME"));
+//                constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
+//            }
+//        }
+//        if (constraint != null) {
+//            returnSet.add(constraint);
+//        }
+//
+//        return returnSet;
+//    }
+
+    @Override
+    public boolean supportsRestrictForeignKeys() {
+        return false;
+    }
+
+    @Override
+    public int getDataTypeMaxParameters(String dataTypeName) {
+        //noinspection HardCodedStringLiteral
+        if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
+            return 0;
+        }
+        //noinspection HardCodedStringLiteral
+        if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
+            return 0;
+        }
+        return super.getDataTypeMaxParameters(dataTypeName);
+    }
+
+    public String getSystemTableWhereClause(String tableNameColumn) {
+        List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
+                "AQ$",
+                "DR$",
+                "SYS_IOT_OVER",
+                "MLOG$_",
+                "RUPD$_",
+                "WM$_",
+                "ISEQ$$_",
+                "USLOG$",
+                "SYS_FBA"));
+
+        for (int i = 0;i<clauses.size(); i++) {
+            clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
+        }
+        return "("+ StringUtil.join(clauses, " AND ") + ")";
+    }
+
+    @Override
+    public boolean jdbcCallsCatalogsSchemas() {
+        return true;
+    }
+
+    public Set<String> getUserDefinedTypes() {
+        if (userDefinedTypes == null) {
+            userDefinedTypes = new HashSet<>();
+            if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
+                try {
+                    try {
+                        //noinspection HardCodedStringLiteral
+                        userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
+                    } catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
+                        //noinspection HardCodedStringLiteral
+                        userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
+                    }
+                } catch (DatabaseException e) {
+                    //ignore error
+                }
+            }
+        }
+
+        return userDefinedTypes;
+    }
+
+    @Override
+    public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
+        //noinspection HardCodedStringLiteral
+        if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
+            return databaseFunction.toString();
+        }
+        if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
+                SequenceCurrentValueFunction)) {
+            String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
+            // replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
+            return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
+
+        }
+
+        return super.generateDatabaseFunctionValue(databaseFunction);
+    }
+
+    @Override
+    public ValidationErrors validate() {
+        ValidationErrors errors = super.validate();
+        DatabaseConnection connection = getConnection();
+        if ((connection == null) || (connection instanceof OfflineConnection)) {
+            //noinspection HardCodedStringLiteral
+            Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
+            return errors;
+        }
+
+        if (!canAccessDbaRecycleBin()) {
+            errors.addWarning(getDbaRecycleBinWarning());
+        }
+
+        return errors;
+
+    }
+
+    public String getDbaRecycleBinWarning() {
+        //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
+        // HardCodedStringLiteral
+        //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
+        return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
+                "constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
+                "referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
+                " issue.\n" +
+                "\n" +
+                "The user you used to connect to the database (" + getConnection().getConnectionUserName() +
+                ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
+                "Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
+                "\n" +
+                "     GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
+    }
+
+    public boolean canAccessDbaRecycleBin() {
+        if (canAccessDbaRecycleBin == null) {
+            DatabaseConnection connection = getConnection();
+            if ((connection == null) || (connection instanceof OfflineConnection)) {
+                return false;
+            }
+
+            Statement statement = null;
+            try {
+                statement = ((JdbcConnection) connection).createStatement();
+                @SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
+                resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
+                this.canAccessDbaRecycleBin = true;
+            } catch (Exception e) {
+                //noinspection HardCodedStringLiteral
+                if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
+                    this.canAccessDbaRecycleBin = false;
+                } else {
+                    //noinspection HardCodedStringLiteral
+                    Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
+                    this.canAccessDbaRecycleBin = false;
+                }
+            } finally {
+                JdbcUtils.close(null, statement);
+            }
+        }
+
+        return canAccessDbaRecycleBin;
+    }
+
+    @Override
+    public boolean supportsNotNullConstraintNames() {
+        return true;
+    }
+
+    /**
+     * Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
+     * the following form (case-insensitive comparison):
+     * 1st character: A-Z
+     * 2..n characters: A-Z0-9$_#
+     * The maximum length of an identifier differs by Oracle version and object type.
+     */
+    public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
+        if ((identifier == null) || (identifier.length() < 1))
+            return false;
+
+        if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
+            return false;
+
+        /*
+         * @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
+         * we do know seem to be supported as 12cR2 long identifiers, so:
+         */
+        return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
+    }
+
+    /**
+     * Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
+     * is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
+     * object types).
+     *
+     * @return the maximum length of an object identifier, in bytes
+     */
+    public int getIdentifierMaximumLength() {
+        try {
+            if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
+                return SHORT_IDENTIFIERS_LENGTH;
+            } else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
+                return SHORT_IDENTIFIERS_LENGTH;
+            } else {
+                return LONG_IDENTIFIERS_LEGNTH;
+            }
+        } catch (DatabaseException ex) {
+            throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
+        }
+
+    }
+}

+ 165 - 0
sql/dm/flowable-patch/src/main/java/liquibase/datatype/core/BooleanType.java

@@ -0,0 +1,165 @@
+package liquibase.datatype.core;
+
+import liquibase.change.core.LoadDataChange;
+import liquibase.database.Database;
+import liquibase.database.core.*;
+import liquibase.datatype.DataTypeInfo;
+import liquibase.datatype.DatabaseDataType;
+import liquibase.datatype.LiquibaseDataType;
+import liquibase.exception.UnexpectedLiquibaseException;
+import liquibase.statement.DatabaseFunction;
+import liquibase.util.StringUtil;
+
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
+public class BooleanType extends LiquibaseDataType {
+
+    @Override
+    public DatabaseDataType toDatabaseDataType(Database database) {
+        String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
+        if ((database instanceof Firebird3Database)) {
+            return new DatabaseDataType("BOOLEAN");
+        }
+
+        if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
+            return new DatabaseDataType("SMALLINT");
+        } else if (database instanceof MSSQLDatabase) {
+            return new DatabaseDataType(database.escapeDataTypeName("bit"));
+        } else if (database instanceof MySQLDatabase) {
+            if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
+                return new DatabaseDataType("BIT", getParameters());
+            }
+            return new DatabaseDataType("BIT", 1);
+        } else if (database instanceof OracleDatabase) {
+            return new DatabaseDataType("NUMBER", 1);
+        } else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
+            return new DatabaseDataType("BIT");
+        } else if (database instanceof DerbyDatabase) {
+            if (((DerbyDatabase) database).supportsBooleanDataType()) {
+                return new DatabaseDataType("BOOLEAN");
+            } else {
+                return new DatabaseDataType("SMALLINT");
+            }
+        } else if (database instanceof DB2Database) {
+            if (((DB2Database) database).supportsBooleanDataType())
+                return new DatabaseDataType("BOOLEAN");
+            else
+                return new DatabaseDataType("SMALLINT");
+        } else if (database instanceof HsqlDatabase) {
+            return new DatabaseDataType("BOOLEAN");
+        } else if (database instanceof PostgresDatabase) {
+            if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
+                return new DatabaseDataType("BIT", getParameters());
+            }
+        } else if (database instanceof DmDatabase) { // dhb52: DM Support
+            return new DatabaseDataType("bit");
+        }
+
+        return super.toDatabaseDataType(database);
+    }
+
+    @Override
+    public String objectToSql(Object value, Database database) {
+        if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
+            return null;
+        }
+
+        String returnValue;
+        if (value instanceof String) {
+            value = ((String) value).replaceAll("'", "");
+            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))) {
+                returnValue = this.getTrueBooleanValue(database);
+            } else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
+                ((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
+                returnValue = this.getFalseBooleanValue(database);
+            } else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
+                returnValue = "b'"
+                    + value.toString()
+                    .replace("b", "")
+                    .replace("\"", "")
+                    .replace("::it", "")
+                    + "'::\"bit\"";
+            } else {
+                throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
+            }
+        } else if (value instanceof Long) {
+            if (Long.valueOf(1).equals(value)) {
+                returnValue = this.getTrueBooleanValue(database);
+            } else {
+                returnValue = this.getFalseBooleanValue(database);
+            }
+        } else if (value instanceof Number) {
+            if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
+                returnValue = this.getTrueBooleanValue(database);
+            } else {
+                returnValue = this.getFalseBooleanValue(database);
+            }
+        } else if (value instanceof DatabaseFunction) {
+            return value.toString();
+        } else if (value instanceof Boolean) {
+            if (((Boolean) value)) {
+                returnValue = this.getTrueBooleanValue(database);
+            } else {
+                returnValue = this.getFalseBooleanValue(database);
+            }
+        } else {
+            throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
+        }
+
+        return returnValue;
+    }
+
+    protected boolean isNumericBoolean(Database database) {
+        if (database instanceof Firebird3Database) {
+            return false;
+        }
+        if (database instanceof DerbyDatabase) {
+            return !((DerbyDatabase) database).supportsBooleanDataType();
+        } else if (database instanceof DB2Database) {
+            return !((DB2Database) database).supportsBooleanDataType();
+        }
+        return (database instanceof Db2zDatabase)
+            || (database instanceof FirebirdDatabase)
+            || (database instanceof MSSQLDatabase)
+            || (database instanceof MySQLDatabase)
+            || (database instanceof OracleDatabase)
+            || (database instanceof SQLiteDatabase)
+            || (database instanceof SybaseASADatabase)
+            || (database instanceof SybaseDatabase)
+            || (database instanceof DmDatabase); // dhb52: DM Support
+    }
+
+    /**
+     * The database-specific value to use for "false" "boolean" columns.
+     */
+    public String getFalseBooleanValue(Database database) {
+        if (isNumericBoolean(database)) {
+            return "0";
+        }
+        if (database instanceof InformixDatabase) {
+            return "'f'";
+        }
+        return "FALSE";
+    }
+
+    /**
+     * The database-specific value to use for "true" "boolean" columns.
+     */
+    public String getTrueBooleanValue(Database database) {
+        if (isNumericBoolean(database)) {
+            return "1";
+        }
+        if (database instanceof InformixDatabase) {
+            return "'t'";
+        }
+        return "TRUE";
+    }
+
+    @Override
+    public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
+        return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
+    }
+
+}

+ 2068 - 0
sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java

@@ -0,0 +1,2068 @@
+/* Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.flowable.common.engine.impl;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import javax.naming.InitialContext;
+import javax.sql.DataSource;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.builder.xml.XMLConfigBuilder;
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.datasource.pooled.PooledDataSource;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
+import org.apache.ibatis.transaction.TransactionFactory;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
+import org.apache.ibatis.type.ArrayTypeHandler;
+import org.apache.ibatis.type.BigDecimalTypeHandler;
+import org.apache.ibatis.type.BlobInputStreamTypeHandler;
+import org.apache.ibatis.type.BlobTypeHandler;
+import org.apache.ibatis.type.BooleanTypeHandler;
+import org.apache.ibatis.type.ByteTypeHandler;
+import org.apache.ibatis.type.ClobTypeHandler;
+import org.apache.ibatis.type.DateOnlyTypeHandler;
+import org.apache.ibatis.type.DateTypeHandler;
+import org.apache.ibatis.type.DoubleTypeHandler;
+import org.apache.ibatis.type.FloatTypeHandler;
+import org.apache.ibatis.type.IntegerTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.LongTypeHandler;
+import org.apache.ibatis.type.NClobTypeHandler;
+import org.apache.ibatis.type.NStringTypeHandler;
+import org.apache.ibatis.type.ShortTypeHandler;
+import org.apache.ibatis.type.SqlxmlTypeHandler;
+import org.apache.ibatis.type.StringTypeHandler;
+import org.apache.ibatis.type.TimeOnlyTypeHandler;
+import org.apache.ibatis.type.TypeHandlerRegistry;
+import org.flowable.common.engine.api.FlowableException;
+import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
+import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
+import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
+import org.flowable.common.engine.api.engine.EngineLifecycleListener;
+import org.flowable.common.engine.impl.agenda.AgendaOperationRunner;
+import org.flowable.common.engine.impl.cfg.CommandExecutorImpl;
+import org.flowable.common.engine.impl.cfg.IdGenerator;
+import org.flowable.common.engine.impl.cfg.TransactionContextFactory;
+import org.flowable.common.engine.impl.cfg.standalone.StandaloneMybatisTransactionContextFactory;
+import org.flowable.common.engine.impl.db.CommonDbSchemaManager;
+import org.flowable.common.engine.impl.db.DbSqlSessionFactory;
+import org.flowable.common.engine.impl.db.LogSqlExecutionTimePlugin;
+import org.flowable.common.engine.impl.db.MybatisTypeAliasConfigurator;
+import org.flowable.common.engine.impl.db.MybatisTypeHandlerConfigurator;
+import org.flowable.common.engine.impl.db.SchemaManager;
+import org.flowable.common.engine.impl.event.EventDispatchAction;
+import org.flowable.common.engine.impl.event.FlowableEventDispatcherImpl;
+import org.flowable.common.engine.impl.interceptor.Command;
+import org.flowable.common.engine.impl.interceptor.CommandConfig;
+import org.flowable.common.engine.impl.interceptor.CommandContextFactory;
+import org.flowable.common.engine.impl.interceptor.CommandContextInterceptor;
+import org.flowable.common.engine.impl.interceptor.CommandExecutor;
+import org.flowable.common.engine.impl.interceptor.CommandInterceptor;
+import org.flowable.common.engine.impl.interceptor.CrDbRetryInterceptor;
+import org.flowable.common.engine.impl.interceptor.DefaultCommandInvoker;
+import org.flowable.common.engine.impl.interceptor.LogInterceptor;
+import org.flowable.common.engine.impl.interceptor.SessionFactory;
+import org.flowable.common.engine.impl.interceptor.TransactionContextInterceptor;
+import org.flowable.common.engine.impl.lock.LockManager;
+import org.flowable.common.engine.impl.lock.LockManagerImpl;
+import org.flowable.common.engine.impl.logging.LoggingListener;
+import org.flowable.common.engine.impl.logging.LoggingSession;
+import org.flowable.common.engine.impl.logging.LoggingSessionFactory;
+import org.flowable.common.engine.impl.persistence.GenericManagerFactory;
+import org.flowable.common.engine.impl.persistence.StrongUuidGenerator;
+import org.flowable.common.engine.impl.persistence.cache.EntityCache;
+import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl;
+import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManager;
+import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManagerImpl;
+import org.flowable.common.engine.impl.persistence.entity.Entity;
+import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManager;
+import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManagerImpl;
+import org.flowable.common.engine.impl.persistence.entity.TableDataManager;
+import org.flowable.common.engine.impl.persistence.entity.TableDataManagerImpl;
+import org.flowable.common.engine.impl.persistence.entity.data.ByteArrayDataManager;
+import org.flowable.common.engine.impl.persistence.entity.data.PropertyDataManager;
+import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisByteArrayDataManager;
+import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisPropertyDataManager;
+import org.flowable.common.engine.impl.runtime.Clock;
+import org.flowable.common.engine.impl.service.CommonEngineServiceImpl;
+import org.flowable.common.engine.impl.util.DefaultClockImpl;
+import org.flowable.common.engine.impl.util.IoUtil;
+import org.flowable.common.engine.impl.util.ReflectUtil;
+import org.flowable.eventregistry.api.EventRegistryEventConsumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public abstract class AbstractEngineConfiguration {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    /** The tenant id indicating 'no tenant' */
+    public static final String NO_TENANT_ID = "";
+
+    /**
+     * 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.
+     */
+    public static final String DB_SCHEMA_UPDATE_FALSE = "false";
+    public static final String DB_SCHEMA_UPDATE_CREATE = "create";
+    public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop";
+
+    /**
+     * Creates the schema when the form engine is being created and drops the schema when the form engine is being closed.
+     */
+    public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create";
+
+    /**
+     * Upon building of the process engine, a check is performed and an update of the schema is performed if it is necessary.
+     */
+    public static final String DB_SCHEMA_UPDATE_TRUE = "true";
+
+    protected boolean forceCloseMybatisConnectionPool = true;
+
+    protected String databaseType;
+    protected String jdbcDriver = "org.h2.Driver";
+    protected String jdbcUrl = "jdbc:h2:tcp://localhost/~/flowable";
+    protected String jdbcUsername = "sa";
+    protected String jdbcPassword = "";
+    protected String dataSourceJndiName;
+    protected int jdbcMaxActiveConnections = 16;
+    protected int jdbcMaxIdleConnections = 8;
+    protected int jdbcMaxCheckoutTime;
+    protected int jdbcMaxWaitTime;
+    protected boolean jdbcPingEnabled;
+    protected String jdbcPingQuery;
+    protected int jdbcPingConnectionNotUsedFor;
+    protected int jdbcDefaultTransactionIsolationLevel;
+    protected DataSource dataSource;
+    protected SchemaManager commonSchemaManager;
+    protected SchemaManager schemaManager;
+    protected Command<Void> schemaManagementCmd;
+
+    protected String databaseSchemaUpdate = DB_SCHEMA_UPDATE_FALSE;
+
+    /**
+     * Whether to use a lock when performing the database schema create or update operations.
+     */
+    protected boolean useLockForDatabaseSchemaUpdate = false;
+
+    protected String xmlEncoding = "UTF-8";
+
+    // COMMAND EXECUTORS ///////////////////////////////////////////////
+
+    protected CommandExecutor commandExecutor;
+    protected Collection<? extends CommandInterceptor> defaultCommandInterceptors;
+    protected CommandConfig defaultCommandConfig;
+    protected CommandConfig schemaCommandConfig;
+    protected CommandContextFactory commandContextFactory;
+    protected CommandInterceptor commandInvoker;
+
+    protected AgendaOperationRunner agendaOperationRunner = (commandContext, runnable) -> runnable.run();
+
+    protected List<CommandInterceptor> customPreCommandInterceptors;
+    protected List<CommandInterceptor> customPostCommandInterceptors;
+    protected List<CommandInterceptor> commandInterceptors;
+
+    protected Map<String, AbstractEngineConfiguration> engineConfigurations = new HashMap<>();
+    protected Map<String, AbstractServiceConfiguration> serviceConfigurations = new HashMap<>();
+
+    protected ClassLoader classLoader;
+    /**
+     * Either use Class.forName or ClassLoader.loadClass for class loading. See http://forums.activiti.org/content/reflectutilloadclass-and-custom- classloader
+     */
+    protected boolean useClassForNameClassLoading = true;
+
+    protected List<EngineLifecycleListener> engineLifecycleListeners;
+
+    // Event Registry //////////////////////////////////////////////////
+    protected Map<String, EventRegistryEventConsumer> eventRegistryEventConsumers = new HashMap<>();
+
+    // MYBATIS SQL SESSION FACTORY /////////////////////////////////////
+
+    protected boolean isDbHistoryUsed = true;
+    protected DbSqlSessionFactory dbSqlSessionFactory;
+    protected SqlSessionFactory sqlSessionFactory;
+    protected TransactionFactory transactionFactory;
+    protected TransactionContextFactory transactionContextFactory;
+
+    /**
+     * If set to true, enables bulk insert (grouping sql inserts together). Default true.
+     * For some databases (eg DB2+z/OS) needs to be set to false.
+     */
+    protected boolean isBulkInsertEnabled = true;
+
+    /**
+     * 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
+     * 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.
+     * <p>
+     * By default: 100 (55 for mssql server as it has a hard limit of 2000 parameters in a statement)
+     */
+    protected int maxNrOfStatementsInBulkInsert = 100;
+
+    public int DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER = 55; // currently Execution has most params (35). 2000 / 35 = 57.
+
+    protected String mybatisMappingFile;
+    protected Set<Class<?>> customMybatisMappers;
+    protected Set<String> customMybatisXMLMappers;
+    protected List<Interceptor> customMybatisInterceptors;
+
+    protected Set<String> dependentEngineMyBatisXmlMappers;
+    protected List<MybatisTypeAliasConfigurator> dependentEngineMybatisTypeAliasConfigs;
+    protected List<MybatisTypeHandlerConfigurator> dependentEngineMybatisTypeHandlerConfigs;
+
+    // SESSION FACTORIES ///////////////////////////////////////////////
+    protected List<SessionFactory> customSessionFactories;
+    protected Map<Class<?>, SessionFactory> sessionFactories;
+
+    protected boolean enableEventDispatcher = true;
+    protected FlowableEventDispatcher eventDispatcher;
+    protected List<FlowableEventListener> eventListeners;
+    protected Map<String, List<FlowableEventListener>> typedEventListeners;
+    protected List<EventDispatchAction> additionalEventDispatchActions;
+
+    protected LoggingListener loggingListener;
+
+    protected boolean transactionsExternallyManaged;
+
+    /**
+     * 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.
+     *
+     * If true (default), the {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will be used to determine what needs to happen wrt the database schema.
+     *
+     * 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
+     * correct. The {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will not be used.
+     */
+    protected boolean usingRelationalDatabase = true;
+
+    /**
+     * 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.
+     * Setting {@link #usingRelationalDatabase} to true will automatically imply using a schema.
+     */
+    protected boolean usingSchemaMgmt = true;
+
+    /**
+     * 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
+     * in a table named 'PRE1.ACT_RU_EXECUTION_'.
+     *
+     * <p>
+     * <strong>NOTE: the prefix is not respected by automatic database schema management. If you use {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_CREATE_DROP} or
+     * {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_TRUE}, Flowable will create the database tables using the default names, regardless of the prefix configured here.</strong>
+     */
+    protected String databaseTablePrefix = "";
+
+    /**
+     * Escape character for doing wildcard searches.
+     *
+     * 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 '\';
+     */
+    protected String databaseWildcardEscapeCharacter;
+
+    /**
+     * database catalog to use
+     */
+    protected String databaseCatalog = "";
+
+    /**
+     * 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,
+     * https://jira.codehaus.org/browse/ACT-1062
+     */
+    protected String databaseSchema;
+
+    /**
+     * 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
+     * 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.
+     */
+    protected boolean tablePrefixIsSchema;
+
+    /**
+     * Set to true if the latest version of a definition should be retrieved, ignoring a possible parent deployment id value
+     */
+    protected boolean alwaysLookupLatestDefinitionVersion;
+
+    /**
+     * Set to true if by default lookups should fallback to the default tenant (an empty string by default or a defined tenant value)
+     */
+    protected boolean fallbackToDefaultTenant;
+
+    /**
+     * Default tenant provider that is executed when looking up definitions, in case the global or local fallback to default tenant value is true
+     */
+    protected DefaultTenantProvider defaultTenantProvider = (tenantId, scope, scopeKey) -> NO_TENANT_ID;
+
+    /**
+     * Enables the MyBatis plugin that logs the execution time of sql statements.
+     */
+    protected boolean enableLogSqlExecutionTime;
+
+    protected Properties databaseTypeMappings = getDefaultDatabaseTypeMappings();
+
+    /**
+     * Duration between the checks when acquiring a lock.
+     */
+    protected Duration lockPollRate = Duration.ofSeconds(10);
+
+    /**
+     * Duration to wait for the DB Schema lock before giving up.
+     */
+    protected Duration schemaLockWaitTime = Duration.ofMinutes(5);
+
+    // DATA MANAGERS //////////////////////////////////////////////////////////////////
+
+    protected PropertyDataManager propertyDataManager;
+    protected ByteArrayDataManager byteArrayDataManager;
+    protected TableDataManager tableDataManager;
+
+    // ENTITY MANAGERS ////////////////////////////////////////////////////////////////
+
+    protected PropertyEntityManager propertyEntityManager;
+    protected ByteArrayEntityManager byteArrayEntityManager;
+
+    protected List<EngineDeployer> customPreDeployers;
+    protected List<EngineDeployer> customPostDeployers;
+    protected List<EngineDeployer> deployers;
+
+    // CONFIGURATORS ////////////////////////////////////////////////////////////
+
+    protected boolean enableConfiguratorServiceLoader = true; // Enabled by default. In certain environments this should be set to false (eg osgi)
+    protected List<EngineConfigurator> configurators; // The injected configurators
+    protected List<EngineConfigurator> allConfigurators; // Including auto-discovered configurators
+    protected EngineConfigurator idmEngineConfigurator;
+    protected EngineConfigurator eventRegistryConfigurator;
+
+    public static final String PRODUCT_NAME_POSTGRES = "PostgreSQL";
+    public static final String PRODUCT_NAME_CRDB = "CockroachDB";
+
+    public static final String DATABASE_TYPE_H2 = "h2";
+    public static final String DATABASE_TYPE_HSQL = "hsql";
+    public static final String DATABASE_TYPE_MYSQL = "mysql";
+    public static final String DATABASE_TYPE_ORACLE = "oracle";
+    public static final String DATABASE_TYPE_POSTGRES = "postgres";
+    public static final String DATABASE_TYPE_MSSQL = "mssql";
+    public static final String DATABASE_TYPE_DB2 = "db2";
+    public static final String DATABASE_TYPE_COCKROACHDB = "cockroachdb";
+
+    public static Properties getDefaultDatabaseTypeMappings() {
+        Properties databaseTypeMappings = new Properties();
+        databaseTypeMappings.setProperty("H2", DATABASE_TYPE_H2);
+        databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL);
+        databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL);
+        databaseTypeMappings.setProperty("MariaDB", DATABASE_TYPE_MYSQL);
+        databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE);
+        databaseTypeMappings.setProperty(PRODUCT_NAME_POSTGRES, DATABASE_TYPE_POSTGRES);
+        databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL);
+        databaseTypeMappings.setProperty(DATABASE_TYPE_DB2, DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/NT", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/NT64", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2 UDP", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/LINUX", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/LINUX390", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/LINUXX8664", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/LINUXZ64", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/LINUXPPC64", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/LINUXPPC64LE", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/400 SQL", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/6000", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2 UDB iSeries", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/AIX64", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/HPUX", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/HP64", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/SUN", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/SUN64", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/PTX", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2);
+        databaseTypeMappings.setProperty(PRODUCT_NAME_CRDB, DATABASE_TYPE_COCKROACHDB);
+        databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_ORACLE); // dhb52: DM support
+        return databaseTypeMappings;
+    }
+
+    protected Map<Object, Object> beans;
+
+    protected IdGenerator idGenerator;
+    protected boolean usePrefixId;
+
+    protected Clock clock;
+    protected ObjectMapper objectMapper;
+
+    // Variables
+
+    public static final int DEFAULT_GENERIC_MAX_LENGTH_STRING = 4000;
+    public static final int DEFAULT_ORACLE_MAX_LENGTH_STRING = 2000;
+
+    /**
+     * Define a max length for storing String variable types in the database. Mainly used for the Oracle NVARCHAR2 limit of 2000 characters
+     */
+    protected int maxLengthStringVariableType = -1;
+
+    protected void initEngineConfigurations() {
+        addEngineConfiguration(getEngineCfgKey(), getEngineScopeType(), this);
+    }
+
+    // DataSource
+    // ///////////////////////////////////////////////////////////////
+
+    protected void initDataSource() {
+        if (dataSource == null) {
+            if (dataSourceJndiName != null) {
+                try {
+                    dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName);
+                } catch (Exception e) {
+                    throw new FlowableException("couldn't lookup datasource from " + dataSourceJndiName + ": " + e.getMessage(), e);
+                }
+
+            } else if (jdbcUrl != null) {
+                if ((jdbcDriver == null) || (jdbcUsername == null)) {
+                    throw new FlowableException("DataSource or JDBC properties have to be specified in a process engine configuration");
+                }
+
+                logger.debug("initializing datasource to db: {}", jdbcUrl);
+
+                if (logger.isInfoEnabled()) {
+                    logger.info("Configuring Datasource with following properties (omitted password for security)");
+                    logger.info("datasource driver : {}", jdbcDriver);
+                    logger.info("datasource url : {}", jdbcUrl);
+                    logger.info("datasource user name : {}", jdbcUsername);
+                }
+
+                PooledDataSource pooledDataSource = new PooledDataSource(this.getClass().getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);
+
+                if (jdbcMaxActiveConnections > 0) {
+                    pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections);
+                }
+                if (jdbcMaxIdleConnections > 0) {
+                    pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections);
+                }
+                if (jdbcMaxCheckoutTime > 0) {
+                    pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime);
+                }
+                if (jdbcMaxWaitTime > 0) {
+                    pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime);
+                }
+                if (jdbcPingEnabled) {
+                    pooledDataSource.setPoolPingEnabled(true);
+                    if (jdbcPingQuery != null) {
+                        pooledDataSource.setPoolPingQuery(jdbcPingQuery);
+                    }
+                    pooledDataSource.setPoolPingConnectionsNotUsedFor(jdbcPingConnectionNotUsedFor);
+                }
+                if (jdbcDefaultTransactionIsolationLevel > 0) {
+                    pooledDataSource.setDefaultTransactionIsolationLevel(jdbcDefaultTransactionIsolationLevel);
+                }
+                dataSource = pooledDataSource;
+            }
+        }
+
+        if (databaseType == null) {
+            initDatabaseType();
+        }
+    }
+
+    public void initDatabaseType() {
+        Connection connection = null;
+        try {
+            connection = dataSource.getConnection();
+            DatabaseMetaData databaseMetaData = connection.getMetaData();
+            String databaseProductName = databaseMetaData.getDatabaseProductName();
+            logger.debug("database product name: '{}'", databaseProductName);
+
+            // CRDB does not expose the version through the jdbc driver, so we need to fetch it through version().
+            if (PRODUCT_NAME_POSTGRES.equalsIgnoreCase(databaseProductName)) {
+                try (PreparedStatement preparedStatement = connection.prepareStatement("select version() as version;");
+                     ResultSet resultSet = preparedStatement.executeQuery()) {
+                    String version = null;
+                    if (resultSet.next()) {
+                        version = resultSet.getString("version");
+                    }
+
+                    if (StringUtils.isNotEmpty(version) && version.toLowerCase().startsWith(PRODUCT_NAME_CRDB.toLowerCase())) {
+                        databaseProductName = PRODUCT_NAME_CRDB;
+                        logger.info("CockroachDB version '{}' detected", version);
+                    }
+                }
+            }
+
+            databaseType = databaseTypeMappings.getProperty(databaseProductName);
+            if (databaseType == null) {
+                throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'");
+            }
+            logger.debug("using database type: {}", databaseType);
+
+        } catch (SQLException e) {
+            throw new RuntimeException("Exception while initializing Database connection", e);
+        } finally {
+            try {
+                if (connection != null) {
+                    connection.close();
+                }
+            } catch (SQLException e) {
+                logger.error("Exception while closing the Database connection", e);
+            }
+        }
+
+        // Special care for MSSQL, as it has a hard limit of 2000 params per statement (incl bulk statement).
+        // Especially with executions, with 100 as default, this limit is passed.
+        if (DATABASE_TYPE_MSSQL.equals(databaseType)) {
+            maxNrOfStatementsInBulkInsert = DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER;
+        }
+    }
+
+    public void initSchemaManager() {
+        if (this.commonSchemaManager == null) {
+            this.commonSchemaManager = new CommonDbSchemaManager();
+        }
+    }
+
+    // session factories ////////////////////////////////////////////////////////
+
+    public void addSessionFactory(SessionFactory sessionFactory) {
+        sessionFactories.put(sessionFactory.getSessionType(), sessionFactory);
+    }
+
+    public void initCommandContextFactory() {
+        if (commandContextFactory == null) {
+            commandContextFactory = new CommandContextFactory();
+        }
+    }
+
+    public void initTransactionContextFactory() {
+        if (transactionContextFactory == null) {
+            transactionContextFactory = new StandaloneMybatisTransactionContextFactory();
+        }
+    }
+
+    public void initCommandExecutors() {
+        initDefaultCommandConfig();
+        initSchemaCommandConfig();
+        initCommandInvoker();
+        initCommandInterceptors();
+        initCommandExecutor();
+    }
+
+
+    public void initDefaultCommandConfig() {
+        if (defaultCommandConfig == null) {
+            defaultCommandConfig = new CommandConfig();
+        }
+    }
+
+    public void initSchemaCommandConfig() {
+        if (schemaCommandConfig == null) {
+            schemaCommandConfig = new CommandConfig();
+        }
+    }
+
+    public void initCommandInvoker() {
+        if (commandInvoker == null) {
+            commandInvoker = new DefaultCommandInvoker();
+        }
+    }
+
+    public void initCommandInterceptors() {
+        if (commandInterceptors == null) {
+            commandInterceptors = new ArrayList<>();
+            if (customPreCommandInterceptors != null) {
+                commandInterceptors.addAll(customPreCommandInterceptors);
+            }
+            commandInterceptors.addAll(getDefaultCommandInterceptors());
+            if (customPostCommandInterceptors != null) {
+                commandInterceptors.addAll(customPostCommandInterceptors);
+            }
+            commandInterceptors.add(commandInvoker);
+        }
+    }
+
+    public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
+        if (defaultCommandInterceptors == null) {
+            List<CommandInterceptor> interceptors = new ArrayList<>();
+            interceptors.add(new LogInterceptor());
+
+            if (DATABASE_TYPE_COCKROACHDB.equals(databaseType)) {
+                interceptors.add(new CrDbRetryInterceptor());
+            }
+
+            CommandInterceptor transactionInterceptor = createTransactionInterceptor();
+            if (transactionInterceptor != null) {
+                interceptors.add(transactionInterceptor);
+            }
+
+            if (commandContextFactory != null) {
+                String engineCfgKey = getEngineCfgKey();
+                CommandContextInterceptor commandContextInterceptor = new CommandContextInterceptor(commandContextFactory,
+                    classLoader, useClassForNameClassLoading, clock, objectMapper);
+                engineConfigurations.put(engineCfgKey, this);
+                commandContextInterceptor.setEngineCfgKey(engineCfgKey);
+                commandContextInterceptor.setEngineConfigurations(engineConfigurations);
+                interceptors.add(commandContextInterceptor);
+            }
+
+            if (transactionContextFactory != null) {
+                interceptors.add(new TransactionContextInterceptor(transactionContextFactory));
+            }
+
+            List<CommandInterceptor> additionalCommandInterceptors = getAdditionalDefaultCommandInterceptors();
+            if (additionalCommandInterceptors != null) {
+                interceptors.addAll(additionalCommandInterceptors);
+            }
+
+            defaultCommandInterceptors = interceptors;
+        }
+        return defaultCommandInterceptors;
+    }
+
+    public abstract String getEngineCfgKey();
+
+    public abstract String getEngineScopeType();
+
+    public List<CommandInterceptor> getAdditionalDefaultCommandInterceptors() {
+        return null;
+    }
+
+    public void initCommandExecutor() {
+        if (commandExecutor == null) {
+            CommandInterceptor first = initInterceptorChain(commandInterceptors);
+            commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
+        }
+    }
+
+    public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
+        if (chain == null || chain.isEmpty()) {
+            throw new FlowableException("invalid command interceptor chain configuration: " + chain);
+        }
+        for (int i = 0; i < chain.size() - 1; i++) {
+            chain.get(i).setNext(chain.get(i + 1));
+        }
+        return chain.get(0);
+    }
+
+    public abstract CommandInterceptor createTransactionInterceptor();
+
+
+    public void initBeans() {
+        if (beans == null) {
+            beans = new HashMap<>();
+        }
+    }
+
+    // id generator
+    // /////////////////////////////////////////////////////////////
+
+    public void initIdGenerator() {
+        if (idGenerator == null) {
+            idGenerator = new StrongUuidGenerator();
+        }
+    }
+
+    public void initObjectMapper() {
+        if (objectMapper == null) {
+            objectMapper = new ObjectMapper();
+            objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+        }
+    }
+
+    public void initClock() {
+        if (clock == null) {
+            clock = new DefaultClockImpl();
+        }
+    }
+
+    // Data managers ///////////////////////////////////////////////////////////
+
+    public void initDataManagers() {
+        if (propertyDataManager == null) {
+            propertyDataManager = new MybatisPropertyDataManager(idGenerator);
+        }
+
+        if (byteArrayDataManager == null) {
+            byteArrayDataManager = new MybatisByteArrayDataManager(idGenerator);
+        }
+    }
+
+    // Entity managers //////////////////////////////////////////////////////////
+
+    public void initEntityManagers() {
+        if (propertyEntityManager == null) {
+            propertyEntityManager = new PropertyEntityManagerImpl(this, propertyDataManager);
+        }
+
+        if (byteArrayEntityManager == null) {
+            byteArrayEntityManager = new ByteArrayEntityManagerImpl(byteArrayDataManager, getEngineCfgKey(), this::getEventDispatcher);
+        }
+
+        if (tableDataManager == null) {
+            tableDataManager = new TableDataManagerImpl(this);
+        }
+    }
+
+    // services
+    // /////////////////////////////////////////////////////////////////
+
+    protected void initService(Object service) {
+        if (service instanceof CommonEngineServiceImpl) {
+            ((CommonEngineServiceImpl) service).setCommandExecutor(commandExecutor);
+        }
+    }
+
+    // myBatis SqlSessionFactory
+    // ////////////////////////////////////////////////
+
+    public void initSessionFactories() {
+        if (sessionFactories == null) {
+            sessionFactories = new HashMap<>();
+
+            if (usingRelationalDatabase) {
+                initDbSqlSessionFactory();
+            }
+
+            addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class));
+
+            if (isLoggingSessionEnabled()) {
+                if (!sessionFactories.containsKey(LoggingSession.class)) {
+                    LoggingSessionFactory loggingSessionFactory = new LoggingSessionFactory();
+                    loggingSessionFactory.setLoggingListener(loggingListener);
+                    loggingSessionFactory.setObjectMapper(objectMapper);
+                    sessionFactories.put(LoggingSession.class, loggingSessionFactory);
+                }
+            }
+
+            commandContextFactory.setSessionFactories(sessionFactories);
+
+        } else {
+            if (usingRelationalDatabase) {
+                initDbSqlSessionFactoryEntitySettings();
+            }
+        }
+
+        if (customSessionFactories != null) {
+            for (SessionFactory sessionFactory : customSessionFactories) {
+                addSessionFactory(sessionFactory);
+            }
+        }
+    }
+
+    public void initDbSqlSessionFactory() {
+        if (dbSqlSessionFactory == null) {
+            dbSqlSessionFactory = createDbSqlSessionFactory();
+        }
+        dbSqlSessionFactory.setDatabaseType(databaseType);
+        dbSqlSessionFactory.setSqlSessionFactory(sqlSessionFactory);
+        dbSqlSessionFactory.setDbHistoryUsed(isDbHistoryUsed);
+        dbSqlSessionFactory.setDatabaseTablePrefix(databaseTablePrefix);
+        dbSqlSessionFactory.setTablePrefixIsSchema(tablePrefixIsSchema);
+        dbSqlSessionFactory.setDatabaseCatalog(databaseCatalog);
+        dbSqlSessionFactory.setDatabaseSchema(databaseSchema);
+        dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(maxNrOfStatementsInBulkInsert);
+
+        initDbSqlSessionFactoryEntitySettings();
+
+        addSessionFactory(dbSqlSessionFactory);
+    }
+
+    public DbSqlSessionFactory createDbSqlSessionFactory() {
+        return new DbSqlSessionFactory(usePrefixId);
+    }
+
+    protected abstract void initDbSqlSessionFactoryEntitySettings();
+
+    protected void defaultInitDbSqlSessionFactoryEntitySettings(List<Class<? extends Entity>> insertOrder, List<Class<? extends Entity>> deleteOrder) {
+        if (insertOrder != null) {
+            for (Class<? extends Entity> clazz : insertOrder) {
+                dbSqlSessionFactory.getInsertionOrder().add(clazz);
+
+                if (isBulkInsertEnabled) {
+                    dbSqlSessionFactory.getBulkInserteableEntityClasses().add(clazz);
+                }
+            }
+        }
+
+        if (deleteOrder != null) {
+            for (Class<? extends Entity> clazz : deleteOrder) {
+                dbSqlSessionFactory.getDeletionOrder().add(clazz);
+            }
+        }
+    }
+
+    public void initTransactionFactory() {
+        if (transactionFactory == null) {
+            if (transactionsExternallyManaged) {
+                transactionFactory = new ManagedTransactionFactory();
+                Properties properties = new Properties();
+                properties.put("closeConnection", "false");
+                this.transactionFactory.setProperties(properties);
+            } else {
+                transactionFactory = new JdbcTransactionFactory();
+            }
+        }
+    }
+
+    public void initSqlSessionFactory() {
+        if (sqlSessionFactory == null) {
+            InputStream inputStream = null;
+            try {
+                inputStream = getMyBatisXmlConfigurationStream();
+
+                Environment environment = new Environment("default", transactionFactory, dataSource);
+                Reader reader = new InputStreamReader(inputStream);
+                Properties properties = new Properties();
+                properties.put("prefix", databaseTablePrefix);
+
+                String wildcardEscapeClause = "";
+                if ((databaseWildcardEscapeCharacter != null) && (databaseWildcardEscapeCharacter.length() != 0)) {
+                    wildcardEscapeClause = " escape '" + databaseWildcardEscapeCharacter + "'";
+                }
+                properties.put("wildcardEscapeClause", wildcardEscapeClause);
+
+                // set default properties
+                properties.put("limitBefore", "");
+                properties.put("limitAfter", "");
+                properties.put("limitBetween", "");
+                properties.put("limitBeforeNativeQuery", "");
+                properties.put("limitAfterNativeQuery", "");
+                properties.put("blobType", "BLOB");
+                properties.put("boolValue", "TRUE");
+
+                if (databaseType != null) {
+                    properties.load(getResourceAsStream(pathToEngineDbProperties()));
+                }
+
+                Configuration configuration = initMybatisConfiguration(environment, reader, properties);
+                sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
+
+            } catch (Exception e) {
+                throw new FlowableException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e);
+            } finally {
+                IoUtil.closeSilently(inputStream);
+            }
+        } else {
+            // This is needed when the SQL Session Factory is created by another engine.
+            // When custom XML Mappers are registered with this engine they need to be loaded in the configuration as well
+            applyCustomMybatisCustomizations(sqlSessionFactory.getConfiguration());
+        }
+    }
+
+    public String pathToEngineDbProperties() {
+        return "org/flowable/common/db/properties/" + databaseType + ".properties";
+    }
+
+    public Configuration initMybatisConfiguration(Environment environment, Reader reader, Properties properties) {
+        XMLConfigBuilder parser = new XMLConfigBuilder(reader, "", properties);
+        Configuration configuration = parser.getConfiguration();
+
+        if (databaseType != null) {
+            configuration.setDatabaseId(databaseType);
+        }
+
+        configuration.setEnvironment(environment);
+
+        initMybatisTypeHandlers(configuration);
+        initCustomMybatisInterceptors(configuration);
+        if (isEnableLogSqlExecutionTime()) {
+            initMyBatisLogSqlExecutionTimePlugin(configuration);
+        }
+
+        configuration = parseMybatisConfiguration(parser);
+        return configuration;
+    }
+
+    public void initCustomMybatisMappers(Configuration configuration) {
+        if (getCustomMybatisMappers() != null) {
+            for (Class<?> clazz : getCustomMybatisMappers()) {
+                if (!configuration.hasMapper(clazz)) {
+                    configuration.addMapper(clazz);
+                }
+            }
+        }
+    }
+
+    public void initMybatisTypeHandlers(Configuration configuration) {
+        // When mapping into Map<String, Object> there is currently a problem with MyBatis.
+        // It will return objects which are driver specific.
+        // Therefore we are registering the mappings between Object.class and the specific jdbc type here.
+        // see https://github.com/mybatis/mybatis-3/issues/2216 for more info
+        TypeHandlerRegistry handlerRegistry = configuration.getTypeHandlerRegistry();
+
+        handlerRegistry.register(Object.class, JdbcType.BOOLEAN, new BooleanTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.BIT, new BooleanTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.TINYINT, new ByteTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.SMALLINT, new ShortTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.INTEGER, new IntegerTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.FLOAT, new FloatTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.DOUBLE, new DoubleTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.CHAR, new StringTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.CLOB, new ClobTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.VARCHAR, new StringTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.NVARCHAR, new NStringTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.NCHAR, new NStringTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.NCLOB, new NClobTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.BIGINT, new LongTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.REAL, new BigDecimalTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.DECIMAL, new BigDecimalTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.NUMERIC, new BigDecimalTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.BLOB, new BlobInputStreamTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.DATE, new DateOnlyTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.TIME, new TimeOnlyTypeHandler());
+        handlerRegistry.register(Object.class, JdbcType.TIMESTAMP, new DateTypeHandler());
+
+        handlerRegistry.register(Object.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
+    }
+
+    public void initCustomMybatisInterceptors(Configuration configuration) {
+        if (customMybatisInterceptors!=null){
+            for (Interceptor interceptor :customMybatisInterceptors){
+                configuration.addInterceptor(interceptor);
+            }
+        }
+    }
+
+    public void initMyBatisLogSqlExecutionTimePlugin(Configuration configuration) {
+        configuration.addInterceptor(new LogSqlExecutionTimePlugin());
+    }
+
+    public Configuration parseMybatisConfiguration(XMLConfigBuilder parser) {
+        Configuration configuration = parser.parse();
+
+        applyCustomMybatisCustomizations(configuration);
+        return configuration;
+    }
+
+    protected void applyCustomMybatisCustomizations(Configuration configuration) {
+        initCustomMybatisMappers(configuration);
+
+        if (dependentEngineMybatisTypeAliasConfigs != null) {
+            for (MybatisTypeAliasConfigurator typeAliasConfig : dependentEngineMybatisTypeAliasConfigs) {
+                typeAliasConfig.configure(configuration.getTypeAliasRegistry());
+            }
+        }
+        if (dependentEngineMybatisTypeHandlerConfigs != null) {
+            for (MybatisTypeHandlerConfigurator typeHandlerConfig : dependentEngineMybatisTypeHandlerConfigs) {
+                typeHandlerConfig.configure(configuration.getTypeHandlerRegistry());
+            }
+        }
+
+        parseDependentEngineMybatisXMLMappers(configuration);
+        parseCustomMybatisXMLMappers(configuration);
+    }
+
+    public void parseCustomMybatisXMLMappers(Configuration configuration) {
+        if (getCustomMybatisXMLMappers() != null) {
+            for (String resource : getCustomMybatisXMLMappers()) {
+                parseMybatisXmlMapping(configuration, resource);
+            }
+        }
+    }
+
+    public void parseDependentEngineMybatisXMLMappers(Configuration configuration) {
+        if (getDependentEngineMyBatisXmlMappers() != null) {
+            for (String resource : getDependentEngineMyBatisXmlMappers()) {
+                parseMybatisXmlMapping(configuration, resource);
+            }
+        }
+    }
+
+    protected void parseMybatisXmlMapping(Configuration configuration, String resource) {
+        // see XMLConfigBuilder.mapperElement()
+        XMLMapperBuilder mapperParser = new XMLMapperBuilder(getResourceAsStream(resource), configuration, resource, configuration.getSqlFragments());
+        mapperParser.parse();
+    }
+
+    protected InputStream getResourceAsStream(String resource) {
+        ClassLoader classLoader = getClassLoader();
+        if (classLoader != null) {
+            return getClassLoader().getResourceAsStream(resource);
+        } else {
+            return this.getClass().getClassLoader().getResourceAsStream(resource);
+        }
+    }
+
+    public void setMybatisMappingFile(String file) {
+        this.mybatisMappingFile = file;
+    }
+
+    public String getMybatisMappingFile() {
+        return mybatisMappingFile;
+    }
+
+    public abstract InputStream getMyBatisXmlConfigurationStream();
+
+    public void initConfigurators() {
+
+        allConfigurators = new ArrayList<>();
+        allConfigurators.addAll(getEngineSpecificEngineConfigurators());
+
+        // Configurators that are explicitly added to the config
+        if (configurators != null) {
+            allConfigurators.addAll(configurators);
+        }
+
+        // Auto discovery through ServiceLoader
+        if (enableConfiguratorServiceLoader) {
+            ClassLoader classLoader = getClassLoader();
+            if (classLoader == null) {
+                classLoader = ReflectUtil.getClassLoader();
+            }
+
+            ServiceLoader<EngineConfigurator> configuratorServiceLoader = ServiceLoader.load(EngineConfigurator.class, classLoader);
+            int nrOfServiceLoadedConfigurators = 0;
+            for (EngineConfigurator configurator : configuratorServiceLoader) {
+                allConfigurators.add(configurator);
+                nrOfServiceLoadedConfigurators++;
+            }
+
+            if (nrOfServiceLoadedConfigurators > 0) {
+                logger.info("Found {} auto-discoverable Process Engine Configurator{}", nrOfServiceLoadedConfigurators, nrOfServiceLoadedConfigurators > 1 ? "s" : "");
+            }
+
+            if (!allConfigurators.isEmpty()) {
+
+                // Order them according to the priorities (useful for dependent
+                // configurator)
+                allConfigurators.sort(new Comparator<EngineConfigurator>() {
+
+                    @Override
+                    public int compare(EngineConfigurator configurator1, EngineConfigurator configurator2) {
+                        int priority1 = configurator1.getPriority();
+                        int priority2 = configurator2.getPriority();
+
+                        if (priority1 < priority2) {
+                            return -1;
+                        } else if (priority1 > priority2) {
+                            return 1;
+                        }
+                        return 0;
+                    }
+                });
+
+                // Execute the configurators
+                logger.info("Found {} Engine Configurators in total:", allConfigurators.size());
+                for (EngineConfigurator configurator : allConfigurators) {
+                    logger.info("{} (priority:{})", configurator.getClass(), configurator.getPriority());
+                }
+
+            }
+
+        }
+    }
+
+    public void close() {
+        if (forceCloseMybatisConnectionPool && dataSource instanceof PooledDataSource) {
+            /*
+             * When the datasource is created by a Flowable engine (i.e. it's an instance of PooledDataSource),
+             * the connection pool needs to be closed when closing the engine.
+             * Note that calling forceCloseAll() multiple times (as is the case when running with multiple engine) is ok.
+             */
+            ((PooledDataSource) dataSource).forceCloseAll();
+        }
+    }
+
+    protected List<EngineConfigurator> getEngineSpecificEngineConfigurators() {
+        // meant to be overridden if needed
+        return Collections.emptyList();
+    }
+
+    public void configuratorsBeforeInit() {
+        for (EngineConfigurator configurator : allConfigurators) {
+            logger.info("Executing beforeInit() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
+            configurator.beforeInit(this);
+        }
+    }
+
+    public void configuratorsAfterInit() {
+        for (EngineConfigurator configurator : allConfigurators) {
+            logger.info("Executing configure() of {} (priority:{})", configurator.getClass(), configurator.getPriority());
+            configurator.configure(this);
+        }
+    }
+
+    public LockManager getLockManager(String lockName) {
+        return new LockManagerImpl(commandExecutor, lockName, getLockPollRate(), getEngineCfgKey());
+    }
+
+    // getters and setters
+    // //////////////////////////////////////////////////////
+
+    public abstract String getEngineName();
+
+    public ClassLoader getClassLoader() {
+        return classLoader;
+    }
+
+    public AbstractEngineConfiguration setClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+        return this;
+    }
+
+    public boolean isUseClassForNameClassLoading() {
+        return useClassForNameClassLoading;
+    }
+
+    public AbstractEngineConfiguration setUseClassForNameClassLoading(boolean useClassForNameClassLoading) {
+        this.useClassForNameClassLoading = useClassForNameClassLoading;
+        return this;
+    }
+
+    public void addEngineLifecycleListener(EngineLifecycleListener engineLifecycleListener) {
+        if (this.engineLifecycleListeners == null) {
+            this.engineLifecycleListeners = new ArrayList<>();
+        }
+        this.engineLifecycleListeners.add(engineLifecycleListener);
+    }
+
+    public List<EngineLifecycleListener> getEngineLifecycleListeners() {
+        return engineLifecycleListeners;
+    }
+
+    public AbstractEngineConfiguration setEngineLifecycleListeners(List<EngineLifecycleListener> engineLifecycleListeners) {
+        this.engineLifecycleListeners = engineLifecycleListeners;
+        return this;
+    }
+
+    public String getDatabaseType() {
+        return databaseType;
+    }
+
+    public AbstractEngineConfiguration setDatabaseType(String databaseType) {
+        this.databaseType = databaseType;
+        return this;
+    }
+
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    public AbstractEngineConfiguration setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+        return this;
+    }
+
+    public SchemaManager getSchemaManager() {
+        return schemaManager;
+    }
+
+    public AbstractEngineConfiguration setSchemaManager(SchemaManager schemaManager) {
+        this.schemaManager = schemaManager;
+        return this;
+    }
+
+    public SchemaManager getCommonSchemaManager() {
+        return commonSchemaManager;
+    }
+
+    public AbstractEngineConfiguration setCommonSchemaManager(SchemaManager commonSchemaManager) {
+        this.commonSchemaManager = commonSchemaManager;
+        return this;
+    }
+
+    public Command<Void> getSchemaManagementCmd() {
+        return schemaManagementCmd;
+    }
+
+    public AbstractEngineConfiguration setSchemaManagementCmd(Command<Void> schemaManagementCmd) {
+        this.schemaManagementCmd = schemaManagementCmd;
+        return this;
+    }
+
+    public String getJdbcDriver() {
+        return jdbcDriver;
+    }
+
+    public AbstractEngineConfiguration setJdbcDriver(String jdbcDriver) {
+        this.jdbcDriver = jdbcDriver;
+        return this;
+    }
+
+    public String getJdbcUrl() {
+        return jdbcUrl;
+    }
+
+    public AbstractEngineConfiguration setJdbcUrl(String jdbcUrl) {
+        this.jdbcUrl = jdbcUrl;
+        return this;
+    }
+
+    public String getJdbcUsername() {
+        return jdbcUsername;
+    }
+
+    public AbstractEngineConfiguration setJdbcUsername(String jdbcUsername) {
+        this.jdbcUsername = jdbcUsername;
+        return this;
+    }
+
+    public String getJdbcPassword() {
+        return jdbcPassword;
+    }
+
+    public AbstractEngineConfiguration setJdbcPassword(String jdbcPassword) {
+        this.jdbcPassword = jdbcPassword;
+        return this;
+    }
+
+    public int getJdbcMaxActiveConnections() {
+        return jdbcMaxActiveConnections;
+    }
+
+    public AbstractEngineConfiguration setJdbcMaxActiveConnections(int jdbcMaxActiveConnections) {
+        this.jdbcMaxActiveConnections = jdbcMaxActiveConnections;
+        return this;
+    }
+
+    public int getJdbcMaxIdleConnections() {
+        return jdbcMaxIdleConnections;
+    }
+
+    public AbstractEngineConfiguration setJdbcMaxIdleConnections(int jdbcMaxIdleConnections) {
+        this.jdbcMaxIdleConnections = jdbcMaxIdleConnections;
+        return this;
+    }
+
+    public int getJdbcMaxCheckoutTime() {
+        return jdbcMaxCheckoutTime;
+    }
+
+    public AbstractEngineConfiguration setJdbcMaxCheckoutTime(int jdbcMaxCheckoutTime) {
+        this.jdbcMaxCheckoutTime = jdbcMaxCheckoutTime;
+        return this;
+    }
+
+    public int getJdbcMaxWaitTime() {
+        return jdbcMaxWaitTime;
+    }
+
+    public AbstractEngineConfiguration setJdbcMaxWaitTime(int jdbcMaxWaitTime) {
+        this.jdbcMaxWaitTime = jdbcMaxWaitTime;
+        return this;
+    }
+
+    public boolean isJdbcPingEnabled() {
+        return jdbcPingEnabled;
+    }
+
+    public AbstractEngineConfiguration setJdbcPingEnabled(boolean jdbcPingEnabled) {
+        this.jdbcPingEnabled = jdbcPingEnabled;
+        return this;
+    }
+
+    public int getJdbcPingConnectionNotUsedFor() {
+        return jdbcPingConnectionNotUsedFor;
+    }
+
+    public AbstractEngineConfiguration setJdbcPingConnectionNotUsedFor(int jdbcPingConnectionNotUsedFor) {
+        this.jdbcPingConnectionNotUsedFor = jdbcPingConnectionNotUsedFor;
+        return this;
+    }
+
+    public int getJdbcDefaultTransactionIsolationLevel() {
+        return jdbcDefaultTransactionIsolationLevel;
+    }
+
+    public AbstractEngineConfiguration setJdbcDefaultTransactionIsolationLevel(int jdbcDefaultTransactionIsolationLevel) {
+        this.jdbcDefaultTransactionIsolationLevel = jdbcDefaultTransactionIsolationLevel;
+        return this;
+    }
+
+    public String getJdbcPingQuery() {
+        return jdbcPingQuery;
+    }
+
+    public AbstractEngineConfiguration setJdbcPingQuery(String jdbcPingQuery) {
+        this.jdbcPingQuery = jdbcPingQuery;
+        return this;
+    }
+
+    public String getDataSourceJndiName() {
+        return dataSourceJndiName;
+    }
+
+    public AbstractEngineConfiguration setDataSourceJndiName(String dataSourceJndiName) {
+        this.dataSourceJndiName = dataSourceJndiName;
+        return this;
+    }
+
+    public CommandConfig getSchemaCommandConfig() {
+        return schemaCommandConfig;
+    }
+
+    public AbstractEngineConfiguration setSchemaCommandConfig(CommandConfig schemaCommandConfig) {
+        this.schemaCommandConfig = schemaCommandConfig;
+        return this;
+    }
+
+    public boolean isTransactionsExternallyManaged() {
+        return transactionsExternallyManaged;
+    }
+
+    public AbstractEngineConfiguration setTransactionsExternallyManaged(boolean transactionsExternallyManaged) {
+        this.transactionsExternallyManaged = transactionsExternallyManaged;
+        return this;
+    }
+
+    public Map<Object, Object> getBeans() {
+        return beans;
+    }
+
+    public AbstractEngineConfiguration setBeans(Map<Object, Object> beans) {
+        this.beans = beans;
+        return this;
+    }
+
+    public IdGenerator getIdGenerator() {
+        return idGenerator;
+    }
+
+    public AbstractEngineConfiguration setIdGenerator(IdGenerator idGenerator) {
+        this.idGenerator = idGenerator;
+        return this;
+    }
+
+    public boolean isUsePrefixId() {
+        return usePrefixId;
+    }
+
+    public AbstractEngineConfiguration setUsePrefixId(boolean usePrefixId) {
+        this.usePrefixId = usePrefixId;
+        return this;
+    }
+
+    public String getXmlEncoding() {
+        return xmlEncoding;
+    }
+
+    public AbstractEngineConfiguration setXmlEncoding(String xmlEncoding) {
+        this.xmlEncoding = xmlEncoding;
+        return this;
+    }
+
+    public CommandConfig getDefaultCommandConfig() {
+        return defaultCommandConfig;
+    }
+
+    public AbstractEngineConfiguration setDefaultCommandConfig(CommandConfig defaultCommandConfig) {
+        this.defaultCommandConfig = defaultCommandConfig;
+        return this;
+    }
+
+    public CommandExecutor getCommandExecutor() {
+        return commandExecutor;
+    }
+
+    public AbstractEngineConfiguration setCommandExecutor(CommandExecutor commandExecutor) {
+        this.commandExecutor = commandExecutor;
+        return this;
+    }
+
+    public CommandContextFactory getCommandContextFactory() {
+        return commandContextFactory;
+    }
+
+    public AbstractEngineConfiguration setCommandContextFactory(CommandContextFactory commandContextFactory) {
+        this.commandContextFactory = commandContextFactory;
+        return this;
+    }
+
+    public CommandInterceptor getCommandInvoker() {
+        return commandInvoker;
+    }
+
+    public AbstractEngineConfiguration setCommandInvoker(CommandInterceptor commandInvoker) {
+        this.commandInvoker = commandInvoker;
+        return this;
+    }
+
+    public AgendaOperationRunner getAgendaOperationRunner() {
+        return agendaOperationRunner;
+    }
+
+    public AbstractEngineConfiguration setAgendaOperationRunner(AgendaOperationRunner agendaOperationRunner) {
+        this.agendaOperationRunner = agendaOperationRunner;
+        return this;
+    }
+
+    public List<CommandInterceptor> getCustomPreCommandInterceptors() {
+        return customPreCommandInterceptors;
+    }
+
+    public AbstractEngineConfiguration setCustomPreCommandInterceptors(List<CommandInterceptor> customPreCommandInterceptors) {
+        this.customPreCommandInterceptors = customPreCommandInterceptors;
+        return this;
+    }
+
+    public List<CommandInterceptor> getCustomPostCommandInterceptors() {
+        return customPostCommandInterceptors;
+    }
+
+    public AbstractEngineConfiguration setCustomPostCommandInterceptors(List<CommandInterceptor> customPostCommandInterceptors) {
+        this.customPostCommandInterceptors = customPostCommandInterceptors;
+        return this;
+    }
+
+    public List<CommandInterceptor> getCommandInterceptors() {
+        return commandInterceptors;
+    }
+
+    public AbstractEngineConfiguration setCommandInterceptors(List<CommandInterceptor> commandInterceptors) {
+        this.commandInterceptors = commandInterceptors;
+        return this;
+    }
+
+    public Map<String, AbstractEngineConfiguration> getEngineConfigurations() {
+        return engineConfigurations;
+    }
+
+    public AbstractEngineConfiguration setEngineConfigurations(Map<String, AbstractEngineConfiguration> engineConfigurations) {
+        this.engineConfigurations = engineConfigurations;
+        return this;
+    }
+
+    public void addEngineConfiguration(String key, String scopeType, AbstractEngineConfiguration engineConfiguration) {
+        if (engineConfigurations == null) {
+            engineConfigurations = new HashMap<>();
+        }
+        engineConfigurations.put(key, engineConfiguration);
+        engineConfigurations.put(scopeType, engineConfiguration);
+    }
+
+    public Map<String, AbstractServiceConfiguration> getServiceConfigurations() {
+        return serviceConfigurations;
+    }
+
+    public AbstractEngineConfiguration setServiceConfigurations(Map<String, AbstractServiceConfiguration> serviceConfigurations) {
+        this.serviceConfigurations = serviceConfigurations;
+        return this;
+    }
+
+    public void addServiceConfiguration(String key, AbstractServiceConfiguration serviceConfiguration) {
+        if (serviceConfigurations == null) {
+            serviceConfigurations = new HashMap<>();
+        }
+        serviceConfigurations.put(key, serviceConfiguration);
+    }
+
+    public Map<String, EventRegistryEventConsumer> getEventRegistryEventConsumers() {
+        return eventRegistryEventConsumers;
+    }
+
+    public AbstractEngineConfiguration setEventRegistryEventConsumers(Map<String, EventRegistryEventConsumer> eventRegistryEventConsumers) {
+        this.eventRegistryEventConsumers = eventRegistryEventConsumers;
+        return this;
+    }
+
+    public void addEventRegistryEventConsumer(String key, EventRegistryEventConsumer eventRegistryEventConsumer) {
+        if (eventRegistryEventConsumers == null) {
+            eventRegistryEventConsumers = new HashMap<>();
+        }
+        eventRegistryEventConsumers.put(key, eventRegistryEventConsumer);
+    }
+
+    public AbstractEngineConfiguration setDefaultCommandInterceptors(Collection<? extends CommandInterceptor> defaultCommandInterceptors) {
+        this.defaultCommandInterceptors = defaultCommandInterceptors;
+        return this;
+    }
+
+    public SqlSessionFactory getSqlSessionFactory() {
+        return sqlSessionFactory;
+    }
+
+    public AbstractEngineConfiguration setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
+        this.sqlSessionFactory = sqlSessionFactory;
+        return this;
+    }
+
+    public boolean isDbHistoryUsed() {
+        return isDbHistoryUsed;
+    }
+
+    public AbstractEngineConfiguration setDbHistoryUsed(boolean isDbHistoryUsed) {
+        this.isDbHistoryUsed = isDbHistoryUsed;
+        return this;
+    }
+
+    public DbSqlSessionFactory getDbSqlSessionFactory() {
+        return dbSqlSessionFactory;
+    }
+
+    public AbstractEngineConfiguration setDbSqlSessionFactory(DbSqlSessionFactory dbSqlSessionFactory) {
+        this.dbSqlSessionFactory = dbSqlSessionFactory;
+        return this;
+    }
+
+    public TransactionFactory getTransactionFactory() {
+        return transactionFactory;
+    }
+
+    public AbstractEngineConfiguration setTransactionFactory(TransactionFactory transactionFactory) {
+        this.transactionFactory = transactionFactory;
+        return this;
+    }
+
+    public TransactionContextFactory getTransactionContextFactory() {
+        return transactionContextFactory;
+    }
+
+    public AbstractEngineConfiguration setTransactionContextFactory(TransactionContextFactory transactionContextFactory) {
+        this.transactionContextFactory = transactionContextFactory;
+        return this;
+    }
+
+    public int getMaxNrOfStatementsInBulkInsert() {
+        return maxNrOfStatementsInBulkInsert;
+    }
+
+    public AbstractEngineConfiguration setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) {
+        this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert;
+        return this;
+    }
+
+    public boolean isBulkInsertEnabled() {
+        return isBulkInsertEnabled;
+    }
+
+    public AbstractEngineConfiguration setBulkInsertEnabled(boolean isBulkInsertEnabled) {
+        this.isBulkInsertEnabled = isBulkInsertEnabled;
+        return this;
+    }
+
+    public Set<Class<?>> getCustomMybatisMappers() {
+        return customMybatisMappers;
+    }
+
+    public AbstractEngineConfiguration setCustomMybatisMappers(Set<Class<?>> customMybatisMappers) {
+        this.customMybatisMappers = customMybatisMappers;
+        return this;
+    }
+
+    public Set<String> getCustomMybatisXMLMappers() {
+        return customMybatisXMLMappers;
+    }
+
+    public AbstractEngineConfiguration setCustomMybatisXMLMappers(Set<String> customMybatisXMLMappers) {
+        this.customMybatisXMLMappers = customMybatisXMLMappers;
+        return this;
+    }
+
+    public Set<String> getDependentEngineMyBatisXmlMappers() {
+        return dependentEngineMyBatisXmlMappers;
+    }
+
+    public AbstractEngineConfiguration setCustomMybatisInterceptors(List<Interceptor> customMybatisInterceptors) {
+        this.customMybatisInterceptors = customMybatisInterceptors;
+        return  this;
+    }
+
+    public List<Interceptor> getCustomMybatisInterceptors() {
+        return customMybatisInterceptors;
+    }
+
+    public AbstractEngineConfiguration setDependentEngineMyBatisXmlMappers(Set<String> dependentEngineMyBatisXmlMappers) {
+        this.dependentEngineMyBatisXmlMappers = dependentEngineMyBatisXmlMappers;
+        return this;
+    }
+
+    public List<MybatisTypeAliasConfigurator> getDependentEngineMybatisTypeAliasConfigs() {
+        return dependentEngineMybatisTypeAliasConfigs;
+    }
+
+    public AbstractEngineConfiguration setDependentEngineMybatisTypeAliasConfigs(List<MybatisTypeAliasConfigurator> dependentEngineMybatisTypeAliasConfigs) {
+        this.dependentEngineMybatisTypeAliasConfigs = dependentEngineMybatisTypeAliasConfigs;
+        return this;
+    }
+
+    public List<MybatisTypeHandlerConfigurator> getDependentEngineMybatisTypeHandlerConfigs() {
+        return dependentEngineMybatisTypeHandlerConfigs;
+    }
+
+    public AbstractEngineConfiguration setDependentEngineMybatisTypeHandlerConfigs(List<MybatisTypeHandlerConfigurator> dependentEngineMybatisTypeHandlerConfigs) {
+        this.dependentEngineMybatisTypeHandlerConfigs = dependentEngineMybatisTypeHandlerConfigs;
+        return this;
+    }
+
+    public List<SessionFactory> getCustomSessionFactories() {
+        return customSessionFactories;
+    }
+
+    public AbstractEngineConfiguration addCustomSessionFactory(SessionFactory sessionFactory) {
+        if (customSessionFactories == null) {
+            customSessionFactories = new ArrayList<>();
+        }
+        customSessionFactories.add(sessionFactory);
+        return this;
+    }
+
+    public AbstractEngineConfiguration setCustomSessionFactories(List<SessionFactory> customSessionFactories) {
+        this.customSessionFactories = customSessionFactories;
+        return this;
+    }
+
+    public boolean isUsingRelationalDatabase() {
+        return usingRelationalDatabase;
+    }
+
+    public AbstractEngineConfiguration setUsingRelationalDatabase(boolean usingRelationalDatabase) {
+        this.usingRelationalDatabase = usingRelationalDatabase;
+        return this;
+    }
+
+    public boolean isUsingSchemaMgmt() {
+        return usingSchemaMgmt;
+    }
+
+    public AbstractEngineConfiguration setUsingSchemaMgmt(boolean usingSchema) {
+        this.usingSchemaMgmt = usingSchema;
+        return this;
+    }
+
+    public String getDatabaseTablePrefix() {
+        return databaseTablePrefix;
+    }
+
+    public AbstractEngineConfiguration setDatabaseTablePrefix(String databaseTablePrefix) {
+        this.databaseTablePrefix = databaseTablePrefix;
+        return this;
+    }
+
+    public String getDatabaseWildcardEscapeCharacter() {
+        return databaseWildcardEscapeCharacter;
+    }
+
+    public AbstractEngineConfiguration setDatabaseWildcardEscapeCharacter(String databaseWildcardEscapeCharacter) {
+        this.databaseWildcardEscapeCharacter = databaseWildcardEscapeCharacter;
+        return this;
+    }
+
+    public String getDatabaseCatalog() {
+        return databaseCatalog;
+    }
+
+    public AbstractEngineConfiguration setDatabaseCatalog(String databaseCatalog) {
+        this.databaseCatalog = databaseCatalog;
+        return this;
+    }
+
+    public String getDatabaseSchema() {
+        return databaseSchema;
+    }
+
+    public AbstractEngineConfiguration setDatabaseSchema(String databaseSchema) {
+        this.databaseSchema = databaseSchema;
+        return this;
+    }
+
+    public boolean isTablePrefixIsSchema() {
+        return tablePrefixIsSchema;
+    }
+
+    public AbstractEngineConfiguration setTablePrefixIsSchema(boolean tablePrefixIsSchema) {
+        this.tablePrefixIsSchema = tablePrefixIsSchema;
+        return this;
+    }
+
+    public boolean isAlwaysLookupLatestDefinitionVersion() {
+        return alwaysLookupLatestDefinitionVersion;
+    }
+
+    public AbstractEngineConfiguration setAlwaysLookupLatestDefinitionVersion(boolean alwaysLookupLatestDefinitionVersion) {
+        this.alwaysLookupLatestDefinitionVersion = alwaysLookupLatestDefinitionVersion;
+        return this;
+    }
+
+    public boolean isFallbackToDefaultTenant() {
+        return fallbackToDefaultTenant;
+    }
+
+    public AbstractEngineConfiguration setFallbackToDefaultTenant(boolean fallbackToDefaultTenant) {
+        this.fallbackToDefaultTenant = fallbackToDefaultTenant;
+        return this;
+    }
+
+    /**
+     * @return name of the default tenant
+     * @deprecated use {@link AbstractEngineConfiguration#getDefaultTenantProvider()} instead
+     */
+    @Deprecated
+    public String getDefaultTenantValue() {
+        return getDefaultTenantProvider().getDefaultTenant(null, null, null);
+    }
+
+    public AbstractEngineConfiguration setDefaultTenantValue(String defaultTenantValue) {
+        this.defaultTenantProvider = (tenantId, scope, scopeKey) -> defaultTenantValue;
+        return this;
+    }
+
+    public DefaultTenantProvider getDefaultTenantProvider() {
+        return defaultTenantProvider;
+    }
+
+    public AbstractEngineConfiguration setDefaultTenantProvider(DefaultTenantProvider defaultTenantProvider) {
+        this.defaultTenantProvider = defaultTenantProvider;
+        return this;
+    }
+
+    public boolean isEnableLogSqlExecutionTime() {
+        return enableLogSqlExecutionTime;
+    }
+
+    public void setEnableLogSqlExecutionTime(boolean enableLogSqlExecutionTime) {
+        this.enableLogSqlExecutionTime = enableLogSqlExecutionTime;
+    }
+
+    public Map<Class<?>, SessionFactory> getSessionFactories() {
+        return sessionFactories;
+    }
+
+    public AbstractEngineConfiguration setSessionFactories(Map<Class<?>, SessionFactory> sessionFactories) {
+        this.sessionFactories = sessionFactories;
+        return this;
+    }
+
+    public String getDatabaseSchemaUpdate() {
+        return databaseSchemaUpdate;
+    }
+
+    public AbstractEngineConfiguration setDatabaseSchemaUpdate(String databaseSchemaUpdate) {
+        this.databaseSchemaUpdate = databaseSchemaUpdate;
+        return this;
+    }
+
+    public boolean isUseLockForDatabaseSchemaUpdate() {
+        return useLockForDatabaseSchemaUpdate;
+    }
+
+    public AbstractEngineConfiguration setUseLockForDatabaseSchemaUpdate(boolean useLockForDatabaseSchemaUpdate) {
+        this.useLockForDatabaseSchemaUpdate = useLockForDatabaseSchemaUpdate;
+        return this;
+    }
+
+    public boolean isEnableEventDispatcher() {
+        return enableEventDispatcher;
+    }
+
+    public AbstractEngineConfiguration setEnableEventDispatcher(boolean enableEventDispatcher) {
+        this.enableEventDispatcher = enableEventDispatcher;
+        return this;
+    }
+
+    public FlowableEventDispatcher getEventDispatcher() {
+        return eventDispatcher;
+    }
+
+    public AbstractEngineConfiguration setEventDispatcher(FlowableEventDispatcher eventDispatcher) {
+        this.eventDispatcher = eventDispatcher;
+        return this;
+    }
+
+    public List<FlowableEventListener> getEventListeners() {
+        return eventListeners;
+    }
+
+    public AbstractEngineConfiguration setEventListeners(List<FlowableEventListener> eventListeners) {
+        this.eventListeners = eventListeners;
+        return this;
+    }
+
+    public Map<String, List<FlowableEventListener>> getTypedEventListeners() {
+        return typedEventListeners;
+    }
+
+    public AbstractEngineConfiguration setTypedEventListeners(Map<String, List<FlowableEventListener>> typedEventListeners) {
+        this.typedEventListeners = typedEventListeners;
+        return this;
+    }
+
+    public List<EventDispatchAction> getAdditionalEventDispatchActions() {
+        return additionalEventDispatchActions;
+    }
+
+    public AbstractEngineConfiguration setAdditionalEventDispatchActions(List<EventDispatchAction> additionalEventDispatchActions) {
+        this.additionalEventDispatchActions = additionalEventDispatchActions;
+        return this;
+    }
+
+    public void initEventDispatcher() {
+        if (this.eventDispatcher == null) {
+            this.eventDispatcher = new FlowableEventDispatcherImpl();
+        }
+
+        initAdditionalEventDispatchActions();
+
+        this.eventDispatcher.setEnabled(enableEventDispatcher);
+
+        initEventListeners();
+        initTypedEventListeners();
+    }
+
+    protected void initEventListeners() {
+        if (eventListeners != null) {
+            for (FlowableEventListener listenerToAdd : eventListeners) {
+                this.eventDispatcher.addEventListener(listenerToAdd);
+            }
+        }
+    }
+
+    protected void initAdditionalEventDispatchActions() {
+        if (this.additionalEventDispatchActions == null) {
+            this.additionalEventDispatchActions = new ArrayList<>();
+        }
+    }
+
+    protected void initTypedEventListeners() {
+        if (typedEventListeners != null) {
+            for (Map.Entry<String, List<FlowableEventListener>> listenersToAdd : typedEventListeners.entrySet()) {
+                // Extract types from the given string
+                FlowableEngineEventType[] types = FlowableEngineEventType.getTypesFromString(listenersToAdd.getKey());
+
+                for (FlowableEventListener listenerToAdd : listenersToAdd.getValue()) {
+                    this.eventDispatcher.addEventListener(listenerToAdd, types);
+                }
+            }
+        }
+    }
+
+    public boolean isLoggingSessionEnabled() {
+        return loggingListener != null;
+    }
+
+    public LoggingListener getLoggingListener() {
+        return loggingListener;
+    }
+
+    public void setLoggingListener(LoggingListener loggingListener) {
+        this.loggingListener = loggingListener;
+    }
+
+    public Clock getClock() {
+        return clock;
+    }
+
+    public AbstractEngineConfiguration setClock(Clock clock) {
+        this.clock = clock;
+        return this;
+    }
+
+    public ObjectMapper getObjectMapper() {
+        return objectMapper;
+    }
+
+    public AbstractEngineConfiguration setObjectMapper(ObjectMapper objectMapper) {
+        this.objectMapper = objectMapper;
+        return this;
+    }
+
+    public int getMaxLengthString() {
+        if (maxLengthStringVariableType == -1) {
+            if ("oracle".equalsIgnoreCase(databaseType)) {
+                return DEFAULT_ORACLE_MAX_LENGTH_STRING;
+            } else {
+                return DEFAULT_GENERIC_MAX_LENGTH_STRING;
+            }
+        } else {
+            return maxLengthStringVariableType;
+        }
+    }
+
+    public int getMaxLengthStringVariableType() {
+        return maxLengthStringVariableType;
+    }
+
+    public AbstractEngineConfiguration setMaxLengthStringVariableType(int maxLengthStringVariableType) {
+        this.maxLengthStringVariableType = maxLengthStringVariableType;
+        return this;
+    }
+
+    public PropertyDataManager getPropertyDataManager() {
+        return propertyDataManager;
+    }
+
+    public Duration getLockPollRate() {
+        return lockPollRate;
+    }
+
+    public AbstractEngineConfiguration setLockPollRate(Duration lockPollRate) {
+        this.lockPollRate = lockPollRate;
+        return this;
+    }
+
+    public Duration getSchemaLockWaitTime() {
+        return schemaLockWaitTime;
+    }
+
+    public void setSchemaLockWaitTime(Duration schemaLockWaitTime) {
+        this.schemaLockWaitTime = schemaLockWaitTime;
+    }
+
+    public AbstractEngineConfiguration setPropertyDataManager(PropertyDataManager propertyDataManager) {
+        this.propertyDataManager = propertyDataManager;
+        return this;
+    }
+
+    public PropertyEntityManager getPropertyEntityManager() {
+        return propertyEntityManager;
+    }
+
+    public AbstractEngineConfiguration setPropertyEntityManager(PropertyEntityManager propertyEntityManager) {
+        this.propertyEntityManager = propertyEntityManager;
+        return this;
+    }
+
+    public ByteArrayDataManager getByteArrayDataManager() {
+        return byteArrayDataManager;
+    }
+
+    public AbstractEngineConfiguration setByteArrayDataManager(ByteArrayDataManager byteArrayDataManager) {
+        this.byteArrayDataManager = byteArrayDataManager;
+        return this;
+    }
+
+    public ByteArrayEntityManager getByteArrayEntityManager() {
+        return byteArrayEntityManager;
+    }
+
+    public AbstractEngineConfiguration setByteArrayEntityManager(ByteArrayEntityManager byteArrayEntityManager) {
+        this.byteArrayEntityManager = byteArrayEntityManager;
+        return this;
+    }
+
+    public TableDataManager getTableDataManager() {
+        return tableDataManager;
+    }
+
+    public AbstractEngineConfiguration setTableDataManager(TableDataManager tableDataManager) {
+        this.tableDataManager = tableDataManager;
+        return this;
+    }
+
+    public List<EngineDeployer> getDeployers() {
+        return deployers;
+    }
+
+    public AbstractEngineConfiguration setDeployers(List<EngineDeployer> deployers) {
+        this.deployers = deployers;
+        return this;
+    }
+
+    public List<EngineDeployer> getCustomPreDeployers() {
+        return customPreDeployers;
+    }
+
+    public AbstractEngineConfiguration setCustomPreDeployers(List<EngineDeployer> customPreDeployers) {
+        this.customPreDeployers = customPreDeployers;
+        return this;
+    }
+
+    public List<EngineDeployer> getCustomPostDeployers() {
+        return customPostDeployers;
+    }
+
+    public AbstractEngineConfiguration setCustomPostDeployers(List<EngineDeployer> customPostDeployers) {
+        this.customPostDeployers = customPostDeployers;
+        return this;
+    }
+
+    public boolean isEnableConfiguratorServiceLoader() {
+        return enableConfiguratorServiceLoader;
+    }
+
+    public AbstractEngineConfiguration setEnableConfiguratorServiceLoader(boolean enableConfiguratorServiceLoader) {
+        this.enableConfiguratorServiceLoader = enableConfiguratorServiceLoader;
+        return this;
+    }
+
+    public List<EngineConfigurator> getConfigurators() {
+        return configurators;
+    }
+
+    public AbstractEngineConfiguration addConfigurator(EngineConfigurator configurator) {
+        if (configurators == null) {
+            configurators = new ArrayList<>();
+        }
+        configurators.add(configurator);
+        return this;
+    }
+
+    /**
+     * @return All {@link EngineConfigurator} instances. Will only contain values after init of the engine.
+     * Use the {@link #getConfigurators()} or {@link #addConfigurator(EngineConfigurator)} methods otherwise.
+     */
+    public List<EngineConfigurator> getAllConfigurators() {
+        return allConfigurators;
+    }
+
+    public AbstractEngineConfiguration setConfigurators(List<EngineConfigurator> configurators) {
+        this.configurators = configurators;
+        return this;
+    }
+
+    public EngineConfigurator getIdmEngineConfigurator() {
+        return idmEngineConfigurator;
+    }
+
+    public AbstractEngineConfiguration setIdmEngineConfigurator(EngineConfigurator idmEngineConfigurator) {
+        this.idmEngineConfigurator = idmEngineConfigurator;
+        return this;
+    }
+
+    public EngineConfigurator getEventRegistryConfigurator() {
+        return eventRegistryConfigurator;
+    }
+
+    public AbstractEngineConfiguration setEventRegistryConfigurator(EngineConfigurator eventRegistryConfigurator) {
+        this.eventRegistryConfigurator = eventRegistryConfigurator;
+        return this;
+    }
+
+    public AbstractEngineConfiguration setForceCloseMybatisConnectionPool(boolean forceCloseMybatisConnectionPool) {
+        this.forceCloseMybatisConnectionPool = forceCloseMybatisConnectionPool;
+        return this;
+    }
+
+    public boolean isForceCloseMybatisConnectionPool() {
+        return forceCloseMybatisConnectionPool;
+    }
+}

+ 1 - 0
sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md

@@ -0,0 +1 @@
+防止IDEA将`.`和`/`混为一谈

+ 21 - 0
sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database

@@ -0,0 +1,21 @@
+liquibase.database.core.CockroachDatabase
+liquibase.database.core.DB2Database
+liquibase.database.core.Db2zDatabase
+liquibase.database.core.DerbyDatabase
+liquibase.database.core.Firebird3Database
+liquibase.database.core.FirebirdDatabase
+liquibase.database.core.H2Database
+liquibase.database.core.HsqlDatabase
+liquibase.database.core.InformixDatabase
+liquibase.database.core.Ingres9Database
+liquibase.database.core.MSSQLDatabase
+liquibase.database.core.MariaDBDatabase
+liquibase.database.core.MockDatabase
+liquibase.database.core.MySQLDatabase
+liquibase.database.core.OracleDatabase
+liquibase.database.core.PostgresDatabase
+liquibase.database.core.SQLiteDatabase
+liquibase.database.core.SybaseASADatabase
+liquibase.database.core.SybaseDatabase
+liquibase.database.core.DmDatabase
+liquibase.database.core.UnsupportedDatabase