diff --git a/zt-module-bpm/zt-module-bpm-server/pom.xml b/zt-module-bpm/zt-module-bpm-server/pom.xml index 50a159d..3d5a500 100644 --- a/zt-module-bpm/zt-module-bpm-server/pom.xml +++ b/zt-module-bpm/zt-module-bpm-server/pom.xml @@ -60,6 +60,18 @@ zt-module-qms-api ${business.qms.version} + + com.zt.plat + zt-module-qms-api + ${revision} + compile + + + com.zt.plat + zt-module-product-api + ${revision} + compile + diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java new file mode 100644 index 0000000..1c86d9e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java @@ -0,0 +1,781 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package com.alibaba.druid.pool; + +import com.alibaba.cloud.commons.lang.StringUtils; +import com.alibaba.druid.VERSION; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; +import com.alibaba.druid.util.JdbcUtils; +import com.alibaba.druid.util.MySqlUtils; +import java.net.SocketTimeoutException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +public class DruidPooledStatement extends PoolableWrapper implements Statement { + private static final Log LOG = LogFactory.getLog(DruidPooledStatement.class); + private final Statement stmt; + protected DruidPooledConnection conn; + protected List resultSetTrace; + protected boolean closed; + protected int fetchRowPeak = -1; + protected int exceptionCount; + + public DruidPooledStatement(DruidPooledConnection conn, Statement stmt) { + super(stmt); + this.conn = conn; + this.stmt = stmt; + } + + protected void addResultSetTrace(ResultSet resultSet) { + if (this.resultSetTrace == null) { + this.resultSetTrace = new ArrayList(1); + } else if (this.resultSetTrace.size() > 0) { + int lastIndex = this.resultSetTrace.size() - 1; + ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(lastIndex); + + try { + if (lastResultSet.isClosed()) { + this.resultSetTrace.set(lastIndex, resultSet); + return; + } + } catch (SQLException var5) { + } + } + + this.resultSetTrace.add(resultSet); + } + + protected void recordFetchRowCount(int fetchRowCount) { + if (this.fetchRowPeak < fetchRowCount) { + this.fetchRowPeak = fetchRowCount; + } + + } + + public int getFetchRowPeak() { + return this.fetchRowPeak; + } + + protected SQLException checkException(Throwable error) throws SQLException { + String sql = null; + if (this instanceof DruidPooledPreparedStatement) { + sql = ((DruidPooledPreparedStatement)this).getSql(); + } + + this.handleSocketTimeout(error); + ++this.exceptionCount; + return this.conn.handleException(error, sql); + } + + protected SQLException checkException(Throwable error, String sql) throws SQLException { + this.handleSocketTimeout(error); + ++this.exceptionCount; + return this.conn.handleException(error, sql); + } + + protected void handleSocketTimeout(Throwable error) throws SQLException { + if (this.conn != null && this.conn.transactionInfo == null && this.conn.holder != null) { + DruidDataSource dataSource = null; + DruidConnectionHolder holder = this.conn.holder; + if (holder.dataSource instanceof DruidDataSource) { + dataSource = (DruidDataSource)holder.dataSource; + } + + if (dataSource != null) { + if (dataSource.killWhenSocketReadTimeout) { + SQLException sqlException = null; + if (error instanceof SQLException) { + sqlException = (SQLException)error; + } + + if (sqlException != null) { + Throwable cause = error.getCause(); + boolean socketReadTimeout = cause instanceof SocketTimeoutException && "Read timed out".equals(cause.getMessage()); + if (socketReadTimeout) { + if (JdbcUtils.isMysqlDbType(dataSource.dbTypeName)) { + String killQuery = MySqlUtils.buildKillQuerySql(this.conn.getConnection(), (SQLException)error); + if (killQuery != null) { + DruidPooledConnection killQueryConn = null; + Statement killQueryStmt = null; + + try { + killQueryConn = dataSource.getConnection(1000L); + if (killQueryConn != null) { + killQueryStmt = killQueryConn.createStatement(); + killQueryStmt.execute(killQuery); + if (LOG.isDebugEnabled()) { + LOG.debug(killQuery + " success."); + } + + return; + } + } catch (Exception ex) { + LOG.warn(killQuery + " error.", ex); + return; + } finally { + JdbcUtils.close(killQueryStmt); + JdbcUtils.close(killQueryConn); + } + + } + } + } + } + } + } + } + } + + public DruidPooledConnection getPoolableConnection() { + return this.conn; + } + + public Statement getStatement() { + return this.stmt; + } + + protected void checkOpen() throws SQLException { + if (this.closed) { + Throwable disableError = null; + if (this.conn != null) { + disableError = this.conn.getDisableError(); + } + + if (disableError != null) { + throw new SQLException("statement is closed", disableError); + } else { + throw new SQLException("statement is closed"); + } + } + } + + protected void clearResultSet() { + if (this.resultSetTrace != null) { + for(ResultSet rs : this.resultSetTrace) { + try { + if (!rs.isClosed()) { + rs.close(); + } + } catch (SQLException ex) { + LOG.error("clearResultSet error", ex); + } + } + + this.resultSetTrace.clear(); + } + } + + public void incrementExecuteCount() { + DruidPooledConnection conn = this.getPoolableConnection(); + if (conn != null) { + DruidConnectionHolder holder = conn.getConnectionHolder(); + if (holder != null) { + DruidAbstractDataSource dataSource = holder.getDataSource(); + if (dataSource != null) { + dataSource.incrementExecuteCount(); + } + } + } + } + + public void incrementExecuteBatchCount() { + DruidPooledConnection conn = this.getPoolableConnection(); + if (conn != null) { + DruidConnectionHolder holder = conn.getConnectionHolder(); + if (holder != null) { + if (holder.getDataSource() != null) { + DruidAbstractDataSource dataSource = holder.getDataSource(); + if (dataSource != null) { + dataSource.incrementExecuteBatchCount(); + } + } + } + } + } + + public void incrementExecuteUpdateCount() { + DruidPooledConnection conn = this.getPoolableConnection(); + if (conn != null) { + DruidConnectionHolder holder = conn.getConnectionHolder(); + if (holder != null) { + DruidAbstractDataSource dataSource = holder.getDataSource(); + if (dataSource != null) { + dataSource.incrementExecuteUpdateCount(); + } + } + } + } + + public void incrementExecuteQueryCount() { + DruidPooledConnection conn = this.conn; + if (conn != null) { + DruidConnectionHolder holder = conn.holder; + if (holder != null) { + DruidAbstractDataSource dataSource = holder.dataSource; + if (dataSource != null) { + ++dataSource.executeQueryCount; + } + } + } + } + + protected void transactionRecord(String sql) throws SQLException { + this.conn.transactionRecord(sql); + } + + public final ResultSet executeQuery(String sql) throws SQLException { + this.checkOpen(); + this.incrementExecuteQueryCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + ResultSet var3; + try { + ResultSet rs = this.stmt.executeQuery(sql); + if (rs != null) { + DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs); + this.addResultSetTrace(poolableResultSet); + DruidPooledResultSet var4 = poolableResultSet; + return var4; + } + + var3 = rs; + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final int executeUpdate(String sql) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var2; + try { + var2 = this.stmt.executeUpdate(sql); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var2; + } + + protected final void errorCheck(Throwable t) { + String errorClassName = t.getClass().getName(); + if (errorClassName.endsWith(".CommunicationsException") && this.conn.holder != null && this.conn.holder.dataSource.testWhileIdle) { + DruidConnectionHolder holder = this.conn.holder; + DruidAbstractDataSource dataSource = holder.dataSource; + long currentTimeMillis = System.currentTimeMillis(); + long lastActiveTimeMillis = holder.lastActiveTimeMillis; + if (lastActiveTimeMillis < holder.lastKeepTimeMillis) { + lastActiveTimeMillis = holder.lastKeepTimeMillis; + } + + long idleMillis = currentTimeMillis - lastActiveTimeMillis; + long lastValidIdleMillis = currentTimeMillis - holder.lastActiveTimeMillis; + String errorMsg = "CommunicationsException, druid version " + VERSION.getVersionNumber() + ", jdbcUrl : " + dataSource.jdbcUrl + ", testWhileIdle " + dataSource.testWhileIdle + ", idle millis " + idleMillis + ", minIdle " + dataSource.minIdle + ", poolingCount " + dataSource.getPoolingCount() + ", timeBetweenEvictionRunsMillis " + dataSource.timeBetweenEvictionRunsMillis + ", lastValidIdleMillis " + lastValidIdleMillis + ", driver " + dataSource.driver.getClass().getName(); + if (dataSource.exceptionSorter != null) { + errorMsg = errorMsg + ", exceptionSorter " + dataSource.exceptionSorter.getClass().getName(); + } + + LOG.error(errorMsg); + } + + } + + public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var3; + try { + var3 = this.stmt.executeUpdate(sql, autoGeneratedKeys); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var3; + try { + var3 = this.stmt.executeUpdate(sql, columnIndexes); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final int executeUpdate(String sql, String[] columnNames) throws SQLException { + this.checkOpen(); + this.incrementExecuteUpdateCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + int var3; + try { + var3 = this.stmt.executeUpdate(sql, columnNames); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + this.checkOpen(); + this.incrementExecuteCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + boolean var3; + try { + var3 = this.stmt.execute(sql, autoGeneratedKeys); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final boolean execute(String sql, int[] columnIndexes) throws SQLException { + this.checkOpen(); + this.incrementExecuteCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + boolean var3; + try { + var3 = this.stmt.execute(sql, columnIndexes); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + public final boolean execute(String sql, String[] columnNames) throws SQLException { + this.checkOpen(); + this.incrementExecuteCount(); + this.transactionRecord(sql); + this.conn.beforeExecute(); + + boolean var3; + try { + var3 = this.stmt.execute(sql, columnNames); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t, sql); + } finally { + this.conn.afterExecute(); + } + + return var3; + } + + + public int getMaxFieldSize() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getMaxFieldSize(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void close() throws SQLException { + if (!this.closed) { + this.clearResultSet(); + if (this.stmt != null) { + this.stmt.close(); + } + + this.closed = true; + DruidConnectionHolder connHolder = this.conn.getConnectionHolder(); + if (connHolder != null) { + connHolder.removeTrace(this); + } + + } + } + + public void setMaxFieldSize(int max) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setMaxFieldSize(max); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getMaxRows() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getMaxRows(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setMaxRows(int max) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setMaxRows(max); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void setEscapeProcessing(boolean enable) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setEscapeProcessing(enable); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getQueryTimeout() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getQueryTimeout(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setQueryTimeout(int seconds) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setQueryTimeout(seconds); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void cancel() throws SQLException { + this.checkOpen(); + + try { + this.stmt.cancel(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final SQLWarning getWarnings() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getWarnings(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void clearWarnings() throws SQLException { + this.checkOpen(); + + try { + this.stmt.clearWarnings(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void setCursorName(String name) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setCursorName(name); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + @Override + public final boolean execute(String sql) throws SQLException { + checkOpen(); + + incrementExecuteCount(); + transactionRecord(sql); + + try { + if (StringUtils.isNotEmpty(sql)){ + sql = sql.replace("TRUE", "1"); + sql = sql.replace("FALSE", "0"); + } + return stmt.execute(sql); + } catch (Throwable t) { + errorCheck(t); + throw checkException(t, sql); + } + } + + public final ResultSet getResultSet() throws SQLException { + this.checkOpen(); + + try { + ResultSet rs = this.stmt.getResultSet(); + if (rs == null) { + return null; + } else { + DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs); + this.addResultSetTrace(poolableResultSet); + return poolableResultSet; + } + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getUpdateCount() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getUpdateCount(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final boolean getMoreResults() throws SQLException { + this.checkOpen(); + + try { + boolean moreResults = this.stmt.getMoreResults(); + if (this.resultSetTrace != null && this.resultSetTrace.size() > 0) { + ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(this.resultSetTrace.size() - 1); + if (lastResultSet instanceof DruidPooledResultSet) { + DruidPooledResultSet pooledResultSet = (DruidPooledResultSet)lastResultSet; + pooledResultSet.closed = true; + } + } + + return moreResults; + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setFetchDirection(int direction) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setFetchDirection(direction); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getFetchDirection() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getFetchDirection(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public void setFetchSize(int rows) throws SQLException { + this.checkOpen(); + + try { + this.stmt.setFetchSize(rows); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getFetchSize() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getFetchSize(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getResultSetConcurrency() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getResultSetConcurrency(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getResultSetType() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getResultSetType(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final void addBatch(String sql) throws SQLException { + this.checkOpen(); + this.transactionRecord(sql); + + try { + this.stmt.addBatch(sql); + } catch (Throwable t) { + throw this.checkException(t, sql); + } + } + + public final void clearBatch() throws SQLException { + if (!this.closed) { + try { + this.stmt.clearBatch(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + } + + public int[] executeBatch() throws SQLException { + this.checkOpen(); + this.incrementExecuteBatchCount(); + this.conn.beforeExecute(); + + int[] var1; + try { + var1 = this.stmt.executeBatch(); + } catch (Throwable t) { + this.errorCheck(t); + throw this.checkException(t); + } finally { + this.conn.afterExecute(); + } + + return var1; + } + + public final Connection getConnection() throws SQLException { + this.checkOpen(); + return this.conn; + } + + public final boolean getMoreResults(int current) throws SQLException { + this.checkOpen(); + + try { + boolean results = this.stmt.getMoreResults(current); + if (this.resultSetTrace != null && this.resultSetTrace.size() > 0) { + ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(this.resultSetTrace.size() - 1); + if (lastResultSet instanceof DruidPooledResultSet) { + DruidPooledResultSet pooledResultSet = (DruidPooledResultSet)lastResultSet; + pooledResultSet.closed = true; + } + } + + return results; + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final ResultSet getGeneratedKeys() throws SQLException { + this.checkOpen(); + + try { + ResultSet rs = this.stmt.getGeneratedKeys(); + DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs); + this.addResultSetTrace(poolableResultSet); + return poolableResultSet; + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final int getResultSetHoldability() throws SQLException { + this.checkOpen(); + + try { + return this.stmt.getResultSetHoldability(); + } catch (Throwable t) { + throw this.checkException(t); + } + } + + public final boolean isClosed() throws SQLException { + return this.closed; + } + + public final void setPoolable(boolean poolable) throws SQLException { + if (!poolable) { + throw new SQLException("not support"); + } + } + + public final boolean isPoolable() throws SQLException { + return false; + } + + public String toString() { + return this.stmt.toString(); + } + + public void closeOnCompletion() throws SQLException { + this.stmt.closeOnCompletion(); + } + + public boolean isCloseOnCompletion() throws SQLException { + return this.stmt.isCloseOnCompletion(); + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/config/RpcConfiguration.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..d4dff89 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.bpm.framework.rpc.config; + +import com.zt.plat.module.capital.api.splyAmountRequest.AmountRequestApi; +import com.zt.plat.module.capital.api.splyAmtCrdtAppl.AmountCreditApplyApi; +import com.zt.plat.module.product.api.MesProcessRoutApi; +import com.zt.plat.module.product.api.plan.MesCompanyPlanApi; +import com.zt.plat.module.product.api.plan.MesFactoryPlanApi; +import com.zt.plat.module.qms.api.task.QmsApi; +import com.zt.plat.module.system.api.dept.DeptApi; +import com.zt.plat.module.system.api.dept.PostApi; +import com.zt.plat.module.system.api.dict.DictDataApi; +import com.zt.plat.module.system.api.permission.PermissionApi; +import com.zt.plat.module.system.api.permission.RoleApi; +import com.zt.plat.module.system.api.sms.SmsSendApi; +import com.zt.plat.module.system.api.user.AdminUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(value = "bpmRpcConfiguration", proxyBeanMethods = false) +@EnableFeignClients(clients = {RoleApi.class, DeptApi.class, PostApi.class, AdminUserApi.class, SmsSendApi.class, DictDataApi.class, + PermissionApi.class, AmountCreditApplyApi.class, MesCompanyPlanApi.class, MesFactoryPlanApi.class, MesProcessRoutApi.class, + QmsApi.class, AmountRequestApi.class}) +public class RpcConfiguration { +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java new file mode 100644 index 0000000..6f57410 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java @@ -0,0 +1,149 @@ +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 AbstractDb2Database) || (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.getClass().isAssignableFrom(DB2Database.class)) { + 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) { + 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 { + 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 DerbyDatabase) { + return !((DerbyDatabase) database).supportsBooleanDataType(); + } else if (database.getClass().isAssignableFrom(DB2Database.class)) { + return !((DB2Database) database).supportsBooleanDataType(); + } + return (database instanceof Db2zDatabase) || (database instanceof DB2Database) || (database instanceof FirebirdDatabase) || (database instanceof + MSSQLDatabase) || (database instanceof MySQLDatabase) || (database instanceof OracleDatabase) || + (database instanceof SQLiteDatabase) || (database instanceof SybaseASADatabase) || (database instanceof + SybaseDatabase) || (database instanceof DmDatabase); + } + + /** + * 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; + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/DmBooleanType.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/DmBooleanType.java new file mode 100644 index 0000000..7f66250 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/datatype/core/DmBooleanType.java @@ -0,0 +1,32 @@ +package liquibase.datatype.core; + +import liquibase.database.Database; +import liquibase.database.core.DmDatabase; +import liquibase.datatype.DataTypeInfo; +import liquibase.datatype.DatabaseDataType; + +@DataTypeInfo( + name = "boolean", + aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, + minParameters = 0, + maxParameters = 0, + priority = 2 +) +public class DmBooleanType extends BooleanType { + + @Override + public boolean supports(Database database) { + if (database instanceof DmDatabase) { + return true; + } + return super.supports(database); + } + + @Override + public DatabaseDataType toDatabaseDataType(Database database) { + if (database instanceof DmDatabase) { + return new DatabaseDataType("NUMBER", 1); + } + return super.toDatabaseDataType(database); + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/snapshot/JdbcDatabaseSnapshot.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/snapshot/JdbcDatabaseSnapshot.java new file mode 100644 index 0000000..1e0a40e --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/liquibase/snapshot/JdbcDatabaseSnapshot.java @@ -0,0 +1,1957 @@ +package liquibase.snapshot; + +import liquibase.CatalogAndSchema; +import liquibase.Scope; +import liquibase.database.AbstractJdbcDatabase; +import liquibase.database.Database; +import liquibase.database.DatabaseConnection; +import liquibase.database.LiquibaseTableNamesFactory; +import liquibase.database.core.*; +import liquibase.database.jvm.JdbcConnection; +import liquibase.exception.DatabaseException; +import liquibase.executor.jvm.ColumnMapRowMapper; +import liquibase.executor.jvm.RowMapperNotNullConstraintsResultSetExtractor; +import liquibase.structure.DatabaseObject; +import liquibase.structure.core.Catalog; +import liquibase.structure.core.Schema; +import liquibase.structure.core.Table; +import liquibase.structure.core.View; +import liquibase.util.JdbcUtil; +import liquibase.util.StringUtil; + +import java.sql.*; +import java.util.*; + +public class JdbcDatabaseSnapshot extends DatabaseSnapshot { + + private boolean warnedAboutDbaRecycleBin; + private static final boolean ignoreWarnAboutDbaRecycleBin = Boolean.getBoolean("liquibase.ignoreRecycleBinWarning"); + + private CachingDatabaseMetaData cachingDatabaseMetaData; + + private Map cachedExpressionMap = null; + + private Set userDefinedTypes; + + public JdbcDatabaseSnapshot(DatabaseObject[] examples, Database database, SnapshotControl snapshotControl) throws DatabaseException, InvalidExampleException { + super(examples, database, snapshotControl); + } + + public JdbcDatabaseSnapshot(DatabaseObject[] examples, Database database) throws DatabaseException, InvalidExampleException { + super(examples, database); + } + + public CachingDatabaseMetaData getMetaDataFromCache() throws SQLException { + if (cachingDatabaseMetaData == null) { + DatabaseMetaData databaseMetaData = null; + if (getDatabase().getConnection() != null) { + databaseMetaData = ((JdbcConnection) getDatabase().getConnection()).getUnderlyingConnection().getMetaData(); + } + + cachingDatabaseMetaData = new CachingDatabaseMetaData(this.getDatabase(), databaseMetaData); + } + return cachingDatabaseMetaData; + } + + public class CachingDatabaseMetaData { + private static final String SQL_FILTER_MATCH_ALL = "%"; + private final DatabaseMetaData databaseMetaData; + private final Database database; + + public CachingDatabaseMetaData(Database database, DatabaseMetaData metaData) { + this.databaseMetaData = metaData; + this.database = database; + } + + public java.sql.DatabaseMetaData getDatabaseMetaData() { + return databaseMetaData; + } + + public List getForeignKeys(final String catalogName, final String schemaName, final String tableName, + final String fkName) throws DatabaseException { + ForeignKeysResultSetCache foreignKeysResultSetCache = new ForeignKeysResultSetCache(database, catalogName, schemaName, tableName, fkName); + ResultSetCache importedKeys = getResultSetCache("getImportedKeys"); + importedKeys.setBulkTracking(!(database instanceof MSSQLDatabase)); + + return importedKeys.get(foreignKeysResultSetCache); + } + + public List getIndexInfo(final String catalogName, final String schemaName, final String tableName, final String indexName) throws DatabaseException, SQLException { + + return getResultSetCache("getIndexInfo").get(new ResultSetCache.UnionResultSetExtractor(database) { + + public boolean isBulkFetchMode; + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(row.getString("TABLE_CAT"), row.getString("TABLE_SCHEM"), database, row.getString("TABLE_NAME"), row.getString("INDEX_NAME")); + } + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, tableName, indexName); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + return getAllCatalogsStringScratchData() != null && database instanceof OracleDatabase; + } + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("TABLE_SCHEM"); + } + + @Override + public List fastFetch() throws SQLException, DatabaseException { + List returnList = new ArrayList<>(); + + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + if (database instanceof OracleDatabase) { + warnAboutDbaRecycleBin(); + + //oracle getIndexInfo is buggy and slow. See Issue 1824548 and http://forums.oracle.com/forums/thread.jspa?messageID=578383򍍏 + String sql = + "SELECT " + + "c.INDEX_NAME, " + + "3 AS TYPE, " + + "c.TABLE_OWNER AS TABLE_SCHEM, " + + "c.TABLE_NAME, " + + "c.COLUMN_NAME, " + + "c.COLUMN_POSITION AS ORDINAL_POSITION, " + + "NULL AS FILTER_CONDITION, " + + "c.INDEX_OWNER, " + + "CASE I.UNIQUENESS WHEN 'UNIQUE' THEN 0 ELSE 1 END AS NON_UNIQUE, " + + "CASE c.DESCEND WHEN 'Y' THEN 'D' WHEN 'DESC' THEN 'D' WHEN 'N' THEN 'A' WHEN 'ASC' THEN 'A' END AS ASC_OR_DESC, " + + "CASE WHEN tablespace_name = (SELECT default_tablespace FROM user_users) " + + "THEN NULL ELSE tablespace_name END AS tablespace_name " + + "FROM ALL_IND_COLUMNS c " + + "JOIN ALL_INDEXES i ON i.owner=c.index_owner AND i.index_name = c.index_name and i.table_owner = c.table_owner " + + "LEFT OUTER JOIN " + (((OracleDatabase) database).canAccessDbaRecycleBin() ? "dba_recyclebin" : "user_recyclebin") + " d ON d.object_name=c.table_name "; + if (!isBulkFetchMode || getAllCatalogsStringScratchData() == null) { + sql += "WHERE c.TABLE_OWNER = '" + database.correctObjectName(catalogAndSchema.getCatalogName(), Schema.class) + "' "; + } else { + sql += "WHERE c.TABLE_OWNER IN ('" + database.correctObjectName(catalogAndSchema.getCatalogName(), Schema.class) + "', " + getAllCatalogsStringScratchData() + ")"; + } + sql += "AND i.OWNER = c.TABLE_OWNER " + + "AND d.object_name IS NULL "; + + + if (!isBulkFetchMode && (tableName != null)) { + sql += " AND c.TABLE_NAME='" + tableName + "'"; + } + + if (!isBulkFetchMode && (indexName != null)) { + sql += " AND c.INDEX_NAME='" + indexName + "'"; + } + + sql += " ORDER BY c.INDEX_NAME, ORDINAL_POSITION"; + + returnList.addAll(setIndexExpressions(executeAndExtract(sql, database))); + } else if (database instanceof MSSQLDatabase) { + String tableCat = "original_db_name()"; + + if (9 <= database.getDatabaseMajorVersion()) { + tableCat = "db_name()"; + } + //fetch additional index info + String sql = "SELECT " + + tableCat + " as TABLE_CAT, " + + "object_schema_name(i.object_id) as TABLE_SCHEM, " + + "object_name(i.object_id) as TABLE_NAME, " + + "CASE is_unique WHEN 1 then 0 else 1 end as NON_UNIQUE, " + + "object_name(i.object_id) as INDEX_QUALIFIER, " + + "i.name as INDEX_NAME, " + + "case i.type when 1 then 1 ELSE 3 end as TYPE, " + + "key_ordinal as ORDINAL_POSITION, " + + "COL_NAME(c.object_id,c.column_id) AS COLUMN_NAME, " + + "case is_descending_key when 0 then 'A' else 'D' end as ASC_OR_DESC, " + + "null as CARDINALITY, " + + "null as PAGES, " + + "i.filter_definition as FILTER_CONDITION, " + + "o.type AS INTERNAL_OBJECT_TYPE, " + + "i.*, " + + "c.*, " + + "s.* " + + "FROM sys.indexes i " + + "join sys.index_columns c on i.object_id=c.object_id and i.index_id=c.index_id " + + "join sys.stats s on i.object_id=s.object_id and i.name=s.name " + + "join sys.objects o on i.object_id=o.object_id " + + "WHERE object_schema_name(i.object_id)='" + database.correctObjectName(catalogAndSchema.getSchemaName(), Schema.class) + "'"; + + if (!isBulkFetchMode && (tableName != null)) { + sql += " AND object_name(i.object_id)='" + database.escapeStringForDatabase(tableName) + "'"; + } + + if (!isBulkFetchMode && (indexName != null)) { + sql += " AND i.name='" + database.escapeStringForDatabase(indexName) + "'"; + } + + sql += "ORDER BY i.object_id, i.index_id, c.key_ordinal"; + + returnList.addAll(executeAndExtract(sql, database)); + + } else if (database instanceof Db2zDatabase) { + List parameters = new ArrayList<>(3); + String sql = "SELECT i.CREATOR AS TABLE_SCHEM, " + + "i.TBNAME AS TABLE_NAME, " + + "i.NAME AS INDEX_NAME, " + + "3 AS TYPE, " + + "k.COLNAME AS COLUMN_NAME, " + + "k.COLSEQ AS ORDINAL_POSITION, " + + "CASE UNIQUERULE WHEN 'D' then 1 else 0 end as NON_UNIQUE, " + + "k.ORDERING AS ORDER, " + + "i.CREATOR AS INDEX_QUALIFIER " + + "FROM SYSIBM.SYSKEYS k " + + "JOIN SYSIBM.SYSINDEXES i " + + "ON k.IXNAME = i.NAME " + + "AND k.IXCREATOR = i.CREATOR " + + "WHERE i.CREATOR = ?"; + parameters.add(database.correctObjectName(catalogAndSchema.getSchemaName(), Schema.class)); + if (!isBulkFetchMode && tableName != null) { + sql += " AND i.TBNAME = ?"; + parameters.add(database.escapeStringForDatabase(tableName)); + } + + if (!isBulkFetchMode && indexName != null) { + sql += " AND i.NAME = ?"; + parameters.add(database.escapeStringForDatabase(indexName)); + } + + sql += "ORDER BY i.NAME, k.COLSEQ"; + + returnList.addAll(executeAndExtract(database, sql, parameters.toArray())); + } else if (!(database instanceof MariaDBDatabase) && database instanceof MySQLDatabase) { + + //mysql 8.0.13 introduced support for indexes on `lower(first_name)` which comes back in an "expression" column + String filterConditionValue = "NULL"; + if (database.getDatabaseMajorVersion() > 8 || (database.getDatabaseMajorVersion() == 8 && ((MySQLDatabase) database).getDatabasePatchVersion() >= 13)) { + filterConditionValue = "EXPRESSION"; + } + + StringBuilder sql = new StringBuilder("SELECT TABLE_CATALOG AS TABLE_CAT, TABLE_SCHEMA AS TABLE_SCHEM,"); + sql.append(" TABLE_NAME, NON_UNIQUE, NULL AS INDEX_QUALIFIER, INDEX_NAME,"); + sql.append(DatabaseMetaData.tableIndexOther); + sql.append(" AS TYPE, SEQ_IN_INDEX AS ORDINAL_POSITION, COLUMN_NAME,"); + sql.append("COLLATION AS ASC_OR_DESC, CARDINALITY, 0 AS PAGES, " + filterConditionValue + " AS FILTER_CONDITION FROM INFORMATION_SCHEMA.STATISTICS WHERE"); + sql.append(" TABLE_SCHEMA = '").append(database.correctObjectName(catalogAndSchema.getCatalogName(), Catalog.class)).append("'"); + + if (!isBulkFetchMode && tableName != null) { + sql.append(" AND TABLE_NAME = '").append(database.escapeStringForDatabase(tableName)).append("'"); + } + + if (!isBulkFetchMode && indexName != null) { + sql.append(" AND INDEX_NAME='").append(database.escapeStringForDatabase(indexName)).append("'"); + } + + sql.append("ORDER BY NON_UNIQUE, INDEX_NAME, SEQ_IN_INDEX"); + + returnList.addAll(executeAndExtract(sql.toString(), database)); + } else { + /* + * If we do not know in which table to look for the index, things get a little bit ugly. + * First, we get a collection of all tables within the catalogAndSchema, then iterate through + * them until we (hopefully) find the index we are looking for. + */ + List tables = new ArrayList<>(); + if (tableName == null) { + // Build a list of all candidate tables in the catalog/schema that might contain the index + for (CachedRow row : getTables(((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema), ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema), null)) { + tables.add(row.getString("TABLE_NAME")); + } + } else { + tables.add(tableName); + } + + // Iterate through all the candidate tables and try to find the index. + for (String tableName1 : tables) { + ResultSet rs = databaseMetaData.getIndexInfo( + ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema), + ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema), + tableName1, + false, + true); + List rows = extract(rs, (database instanceof InformixDatabase)); + returnList.addAll(rows); + } + } + + return returnList; + } + + private List setIndexExpressions(List c) throws DatabaseException, SQLException { + Map expressionMap = getCachedExpressionMap(); + c.forEach(row -> { + row.set("FILTER_CONDITION", null); + String key = row.getString("INDEX_OWNER") + "::" + row.getString("INDEX_NAME") + "::" + + row.getInt("ORDINAL_POSITION"); + CachedRow fromMap = expressionMap.get(key); + if (fromMap != null) { + row.set("FILTER_CONDITION", fromMap.get("COLUMN_EXPRESSION")); + } + }); + return c; + } + + private Map getCachedExpressionMap() throws DatabaseException, SQLException { + if (cachedExpressionMap != null) { + return cachedExpressionMap; + } + String expSql = "SELECT e.column_expression, e.index_owner, e.index_name, e.column_position FROM all_ind_expressions e"; + List ec = executeAndExtract(expSql, database); + cachedExpressionMap = new HashMap<>(); + ec.forEach(row -> { + String key = row.getString("INDEX_OWNER") + "::" + row.getString("INDEX_NAME") + "::" + + row.getInt("COLUMN_POSITION"); + cachedExpressionMap.put(key, row); + }); + return cachedExpressionMap; + } + + @Override + public List bulkFetch() throws SQLException, DatabaseException { + this.isBulkFetchMode = true; + return fastFetch(); + } + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + if (database instanceof OracleDatabase || database instanceof MSSQLDatabase) { + return JdbcDatabaseSnapshot.this.getAllCatalogsStringScratchData() != null || (tableName == null && indexName == null) || super.shouldBulkSelect(schemaKey, resultSetCache); + } + return false; + } + }); + } + + + protected void warnAboutDbaRecycleBin() { + if (!ignoreWarnAboutDbaRecycleBin && !warnedAboutDbaRecycleBin && !(((OracleDatabase) database).canAccessDbaRecycleBin())) { + Scope.getCurrentScope().getLog(getClass()).warning(((OracleDatabase) database).getDbaRecycleBinWarning()); + warnedAboutDbaRecycleBin = true; + } + } + + /** + * Return the columns for the given catalog, schema, table, and column. + */ + public List getColumns(final String catalogName, final String schemaName, final String tableName, final String columnName) throws SQLException, DatabaseException { + + if ((database instanceof MSSQLDatabase) && (userDefinedTypes == null)) { + userDefinedTypes = new HashSet<>(); + DatabaseConnection databaseConnection = database.getConnection(); + if (databaseConnection instanceof JdbcConnection) { + Statement stmt = null; + ResultSet resultSet = null; + try { + stmt = ((JdbcConnection) databaseConnection).getUnderlyingConnection().createStatement(); + resultSet = stmt.executeQuery("select name from " + (catalogName == null ? "" : "[" + catalogName + "].") + "sys.types where is_user_defined=1"); + while (resultSet.next()) { + userDefinedTypes.add(resultSet.getString("name").toLowerCase()); + } + } finally { + JdbcUtil.close(resultSet, stmt); + } + } + } + GetColumnResultSetCache getColumnResultSetCache = new GetColumnResultSetCache(database, catalogName, + schemaName, tableName, columnName); + return getResultSetCache("getColumns").get(getColumnResultSetCache); + } + + /** + * Return the NotNullConstraints for the given catalog, schema, table, and column. + */ + public List getNotNullConst(final String catalogName, final String schemaName, + final String tableName) throws DatabaseException { + if (!(database instanceof OracleDatabase)) { + return Collections.emptyList(); + } + GetNotNullConstraintsResultSetCache getNotNullConstraintsResultSetCache = new GetNotNullConstraintsResultSetCache(database, catalogName, + schemaName, tableName); + return getResultSetCache("getNotNullConst").get(getNotNullConstraintsResultSetCache); + } + + private class GetColumnResultSetCache extends ResultSetCache.SingleResultSetExtractor { + final String catalogName; + final String schemaName; + final String tableName; + final String columnName; + + private GetColumnResultSetCache(Database database, String catalogName, String schemaName, String tableName, String columnName) { + super(database); + this.catalogName = catalogName; + this.schemaName = schemaName; + this.tableName = tableName; + this.columnName = columnName; + } + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(row.getString("TABLE_CAT"), row.getString("TABLE_SCHEM"), database, row.getString("TABLE_NAME"), row.getString("COLUMN_NAME")); + } + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, tableName, columnName); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + String catalogs = getAllCatalogsStringScratchData(); + return catalogs != null && schemaKey != null + && catalogs.contains("'" + schemaKey.toUpperCase() + "'") + && database instanceof OracleDatabase; + } + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("TABLE_SCHEM"); + } + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + LiquibaseTableNamesFactory liquibaseTableNamesFactory = Scope.getCurrentScope().getSingleton(LiquibaseTableNamesFactory.class); + List liquibaseTableNames = liquibaseTableNamesFactory.getLiquibaseTableNames(database); + return liquibaseTableNames.stream().noneMatch(tableName::equalsIgnoreCase); + } + + @Override + public List fastFetchQuery() throws SQLException, DatabaseException { + if (database instanceof OracleDatabase) { + return oracleQuery(false); + } else if (database instanceof MSSQLDatabase) { + return mssqlQuery(false); + } + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + try { + List returnList = + extract( + databaseMetaData.getColumns( + ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema), + escapeForLike(((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema), database), + escapeForLike(tableName, database), + SQL_FILTER_MATCH_ALL) + ); + // + // IF MARIADB OR SQL ANYWHERE + // Query to get actual data types and then map each column to its CachedRow + // + determineActualDataTypes(returnList, tableName); + return returnList; + } catch (SQLException e) { + if (shouldReturnEmptyColumns(e)) { //view with table already dropped. Act like it has no columns. + return new ArrayList<>(); + } else { + throw e; + } + } + } + + @Override + public List bulkFetchQuery() throws SQLException, DatabaseException { + if (database instanceof OracleDatabase) { + return oracleQuery(true); + } else if (database instanceof MSSQLDatabase) { + return mssqlQuery(true); + } + + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + try { + List returnList = + extract(databaseMetaData.getColumns(((AbstractJdbcDatabase) database) + .getJdbcCatalogName(catalogAndSchema), + escapeForLike(((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema), database), + SQL_FILTER_MATCH_ALL, SQL_FILTER_MATCH_ALL)); + // + // IF MARIADB OR SQL ANYWHERE + // Query to get actual data types and then map each column to its CachedRow + // + determineActualDataTypes(returnList, null); + return returnList; + } catch (SQLException e) { + if (shouldReturnEmptyColumns(e)) { + return new ArrayList<>(); + } else { + throw e; + } + } + } + + // + // For MariaDB, query for the data type column so that we can correctly + // set the DATETIME(6) type if specified + // + // For SQL Anywhere, query for the scale column so we can correctly + // set the size unit + // + private void determineActualDataTypes(List returnList, String tableName) throws SQLException { + // + // If not MariaDB / SQL Anywhere then just return + // + if (!(database instanceof MariaDBDatabase || database instanceof SybaseASADatabase)) { + return; + } + + if (database instanceof SybaseASADatabase) { + // + // Query for actual data type for column. The actual SYSTABCOL.scale column value is + // not reported by the DatabaseMetadata.getColumns() query for CHAR-limited (in contrast + // to BYTE-limited) columns, and it is needed to capture the kind if limitation. + // The actual SYSTABCOL.column_type is not reported by the DatabaseMetadata.getColumns() + // query as the IS_GENERATEDCOLUMN columns is missing in the result set, and it is needed to + // capture the kind of column (regular or computed). + // + // See https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/3beaa3956c5f1014883cb0c3e3559cc9.html. + // + String selectStatement = + "SELECT table_name, column_name, scale, column_type FROM SYSTABCOL KEY JOIN SYSTAB KEY JOIN SYSUSER " + + "WHERE user_name = ? AND ? IS NULL OR table_name = ?"; + Connection underlyingConnection = ((JdbcConnection) database.getConnection()).getUnderlyingConnection(); + try (PreparedStatement stmt = underlyingConnection.prepareStatement(selectStatement)) { + stmt.setString(1, schemaName); + stmt.setString(2, tableName); + stmt.setString(3, tableName); + try (ResultSet columnSelectRS = stmt.executeQuery()) { + while (columnSelectRS.next()) { + String selectedTableName = columnSelectRS.getString("table_name"); + String selectedColumnName = columnSelectRS.getString("column_name"); + int selectedScale = columnSelectRS.getInt("scale"); + String selectedColumnType = columnSelectRS.getString("column_type"); + for (CachedRow row : returnList) { + String rowTableName = row.getString("TABLE_NAME"); + String rowColumnName = row.getString("COLUMN_NAME"); + if (rowTableName.equalsIgnoreCase(selectedTableName) && + rowColumnName.equalsIgnoreCase(selectedColumnName)) { + int rowDataType = row.getInt("DATA_TYPE"); + if (rowDataType == Types.VARCHAR || rowDataType == Types.CHAR) { + row.set("scale", selectedScale); + } + row.set("IS_GENERATEDCOLUMN", "C".equals(selectedColumnType) ? "YES" : "NO"); + break; + } + } + } + } + } catch (SQLException sqle) { + throw new RuntimeException(sqle); + // + // Do not stop + // + } + return; + } + + // + // Query for actual data type for column. The actual DATA_TYPE column string is + // not returned by the DatabaseMetadata.getColumns() query, and it is needed + // to capture DATETIME() data types. + // + StringBuilder selectStatement = new StringBuilder( + "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ?"); + if(tableName != null) { + selectStatement.append(" AND TABLE_NAME = ?"); + } + Connection underlyingConnection = ((JdbcConnection) database.getConnection()).getUnderlyingConnection(); + PreparedStatement statement = underlyingConnection.prepareStatement(selectStatement.toString()); + statement.setString(1, schemaName); + if (tableName != null) { + statement.setString(2, tableName); + } + try { + ResultSet columnSelectRS = statement.executeQuery(selectStatement.toString()); + // + // Iterate the result set from the query and match the rows + // to the rows that were returned by getColumns() in order + // to assign the actual DATA_TYPE string to the appropriate row. + // + while (columnSelectRS.next()) { + String selectedTableName = columnSelectRS.getString("TABLE_NAME"); + String selectedColumnName = columnSelectRS.getString("COLUMN_NAME"); + String actualDataType = columnSelectRS.getString("DATA_TYPE"); + for (CachedRow row : returnList) { + String rowTableName = row.getString("TABLE_NAME"); + String rowColumnName = row.getString("COLUMN_NAME"); + String rowTypeName = row.getString("TYPE_NAME"); + int rowDataType = row.getInt("DATA_TYPE"); + if (rowTableName.equalsIgnoreCase(selectedTableName) && + rowColumnName.equalsIgnoreCase(selectedColumnName) && + rowTypeName.equalsIgnoreCase("datetime") && + rowDataType == Types.OTHER && + !rowTypeName.equalsIgnoreCase(actualDataType)) { + row.set("TYPE_NAME", actualDataType); + row.set("DATA_TYPE", Types.TIMESTAMP); + break; + } + } + } + } catch (SQLException sqle) { + // + // Do not stop + // + } + finally { + JdbcUtil.closeStatement(statement); + } + } + + protected boolean shouldReturnEmptyColumns(SQLException e) { + return e.getMessage().contains("references invalid table"); //view with table already dropped. Act like it has no columns. + } + + protected List oracleQuery(boolean bulk) throws DatabaseException, SQLException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + String jdbcSchemaName = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + boolean collectIdentityData = database.getDatabaseMajorVersion() >= OracleDatabase.ORACLE_12C_MAJOR_VERSION; + + String sql = "select NULL AS TABLE_CAT, OWNER AS TABLE_SCHEM, 'NO' as IS_AUTOINCREMENT, cc.COMMENTS AS REMARKS," + + "OWNER, TABLE_NAME, COLUMN_NAME, DATA_TYPE AS DATA_TYPE_NAME, DATA_TYPE_MOD, DATA_TYPE_OWNER, " + + // note: oracle reports DATA_LENGTH=4*CHAR_LENGTH when using VARCHAR( CHAR ), thus BYTEs + "DECODE (c.data_type, 'CHAR', 1, 'VARCHAR2', 12, 'NUMBER', 3, 'LONG', -1, 'DATE', " + "93" + ", 'RAW', -3, 'LONG RAW', -4, 'BLOB', 2004, 'CLOB', 2005, 'BFILE', -13, 'FLOAT', 6, 'TIMESTAMP(6)', 93, 'TIMESTAMP(6) WITH TIME ZONE', -101, 'TIMESTAMP(6) WITH LOCAL TIME ZONE', -102, 'INTERVAL YEAR(2) TO MONTH', -103, 'INTERVAL DAY(2) TO SECOND(6)', -104, 'BINARY_FLOAT', 100, 'BINARY_DOUBLE', 101, 'XMLTYPE', 2009, 1111) AS data_type, " + + "DECODE( CHAR_USED, 'C',CHAR_LENGTH, DATA_LENGTH ) as DATA_LENGTH, " + + "DATA_PRECISION, DATA_SCALE, NULLABLE, COLUMN_ID as ORDINAL_POSITION, DEFAULT_LENGTH, " + + "DATA_DEFAULT, " + + "NUM_BUCKETS, CHARACTER_SET_NAME, " + + "CHAR_COL_DECL_LENGTH, CHAR_LENGTH, " + + "CHAR_USED, VIRTUAL_COLUMN "; + if (collectIdentityData) { + sql += ", DEFAULT_ON_NULL, IDENTITY_COLUMN, ic.GENERATION_TYPE "; + } + sql += "FROM ALL_TAB_COLS c " + + "JOIN ALL_COL_COMMENTS cc USING ( OWNER, TABLE_NAME, COLUMN_NAME ) "; + if (collectIdentityData) { + sql += "LEFT JOIN ALL_TAB_IDENTITY_COLS ic USING (OWNER, TABLE_NAME, COLUMN_NAME ) "; + } + if (!bulk || getAllCatalogsStringScratchData() == null) { + sql += "WHERE OWNER='" + jdbcSchemaName + "' AND hidden_column='NO'"; + } else { + sql += "WHERE OWNER IN ('" + jdbcSchemaName + "', " + getAllCatalogsStringScratchData() + ") AND hidden_column='NO'"; + } + + if (!bulk) { + if (tableName != null) { + sql += " AND TABLE_NAME='" + database.escapeStringForDatabase(tableName) + "'"; + } + if (columnName != null) { + sql += " AND COLUMN_NAME='" + database.escapeStringForDatabase(columnName) + "'"; + } + } + sql += " AND " + ((OracleDatabase) database).getSystemTableWhereClause("TABLE_NAME"); + sql += " ORDER BY OWNER, TABLE_NAME, c.COLUMN_ID"; + + return this.executeAndExtract(sql, database); + } + + + protected List mssqlQuery(boolean bulk) throws DatabaseException, SQLException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + String databaseName = StringUtil.trimToNull(database.correctObjectName(catalogAndSchema.getCatalogName(), Catalog.class)); + String dbIdParam; + String databasePrefix; + if (databaseName == null) { + databasePrefix = ""; + dbIdParam = ""; + } else { + dbIdParam = ", db_id('" + databaseName + "')"; + databasePrefix = "[" + databaseName + "]."; + } + + String sql = "select " + + "db_name(" + (databaseName == null ? "" : "db_id('" + databaseName + "')") + ") AS TABLE_CAT, " + + "object_schema_name(c.object_id" + dbIdParam + ") AS TABLE_SCHEM, " + + "object_name(c.object_id" + dbIdParam + ") AS TABLE_NAME, " + + "c.name AS COLUMN_NAME, " + + "is_filestream AS IS_FILESTREAM, " + + "is_rowguidcol AS IS_ROWGUIDCOL, " + + "CASE WHEN c.is_identity = 'true' THEN 'YES' ELSE 'NO' END as IS_AUTOINCREMENT, " + + "{REMARKS_COLUMN_PLACEHOLDER}" + + "t.name AS TYPE_NAME, " + + "dc.name as COLUMN_DEF_NAME, " + + "dc.definition as COLUMN_DEF, " + + // data type mapping from https://msdn.microsoft.com/en-us/library/ms378878(v=sql.110).aspx + "CASE t.name " + + "WHEN 'bigint' THEN " + java.sql.Types.BIGINT + " " + + "WHEN 'binary' THEN " + java.sql.Types.BINARY + " " + + "WHEN 'bit' THEN " + java.sql.Types.BIT + " " + + "WHEN 'char' THEN " + java.sql.Types.CHAR + " " + + "WHEN 'date' THEN " + java.sql.Types.DATE + " " + + "WHEN 'datetime' THEN " + java.sql.Types.TIMESTAMP + " " + + "WHEN 'datetime2' THEN " + java.sql.Types.TIMESTAMP + " " + + "WHEN 'datetimeoffset' THEN -155 " + + "WHEN 'decimal' THEN " + java.sql.Types.DECIMAL + " " + + "WHEN 'float' THEN " + java.sql.Types.DOUBLE + " " + + "WHEN 'image' THEN " + java.sql.Types.LONGVARBINARY + " " + + "WHEN 'int' THEN " + java.sql.Types.INTEGER + " " + + "WHEN 'money' THEN " + java.sql.Types.DECIMAL + " " + + "WHEN 'nchar' THEN " + java.sql.Types.NCHAR + " " + + "WHEN 'ntext' THEN " + java.sql.Types.LONGNVARCHAR + " " + + "WHEN 'numeric' THEN " + java.sql.Types.NUMERIC + " " + + "WHEN 'nvarchar' THEN " + java.sql.Types.NVARCHAR + " " + + "WHEN 'real' THEN " + Types.REAL + " " + + "WHEN 'smalldatetime' THEN " + java.sql.Types.TIMESTAMP + " " + + "WHEN 'smallint' THEN " + java.sql.Types.SMALLINT + " " + + "WHEN 'smallmoney' THEN " + java.sql.Types.DECIMAL + " " + + "WHEN 'text' THEN " + java.sql.Types.LONGVARCHAR + " " + + "WHEN 'time' THEN " + java.sql.Types.TIME + " " + + "WHEN 'timestamp' THEN " + java.sql.Types.BINARY + " " + + "WHEN 'tinyint' THEN " + java.sql.Types.TINYINT + " " + + "WHEN 'udt' THEN " + java.sql.Types.VARBINARY + " " + + "WHEN 'uniqueidentifier' THEN " + java.sql.Types.CHAR + " " + + "WHEN 'varbinary' THEN " + java.sql.Types.VARBINARY + " " + + "WHEN 'varbinary(max)' THEN " + java.sql.Types.VARBINARY + " " + + "WHEN 'varchar' THEN " + java.sql.Types.VARCHAR + " " + + "WHEN 'varchar(max)' THEN " + java.sql.Types.VARCHAR + " " + + "WHEN 'xml' THEN " + java.sql.Types.LONGVARCHAR + " " + + "WHEN 'LONGNVARCHAR' THEN " + java.sql.Types.SQLXML + " " + + "ELSE " + Types.OTHER + " END AS DATA_TYPE, " + + "CASE WHEN c.is_nullable = 'true' THEN 1 ELSE 0 END AS NULLABLE, " + + "10 as NUM_PREC_RADIX, " + + "c.column_id as ORDINAL_POSITION, " + + "c.scale as DECIMAL_DIGITS, " + + "c.max_length as COLUMN_SIZE, " + + "c.precision as DATA_PRECISION, " + + "c.is_computed as IS_COMPUTED " + + "FROM " + databasePrefix + "sys.columns c " + + "inner join " + databasePrefix + "sys.types t on c.user_type_id=t.user_type_id " + + "{REMARKS_JOIN_PLACEHOLDER}" + + "left outer join " + databasePrefix + "sys.default_constraints dc on dc.parent_column_id = c.column_id AND dc.parent_object_id=c.object_id AND type_desc='DEFAULT_CONSTRAINT' " + + "WHERE object_schema_name(c.object_id" + dbIdParam + ")='" + ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema) + "'"; + + + if (!bulk) { + if (tableName != null) { + sql += " and object_name(c.object_id" + dbIdParam + ")='" + database.escapeStringForDatabase(tableName) + "'"; + } + if (columnName != null) { + sql += " and c.name='" + database.escapeStringForDatabase(columnName) + "'"; + } + } + sql += "order by object_schema_name(c.object_id" + dbIdParam + "), object_name(c.object_id" + dbIdParam + "), c.column_id"; + + + // sys.extended_properties is added to Azure on V12: https://feedback.azure.com/forums/217321-sql-database/suggestions/6549815-add-sys-extended-properties-for-meta-data-support + if ((!((MSSQLDatabase) database).isAzureDb()) // Either NOT AzureDB (=SQL Server 2008 or higher) + || (database.getDatabaseMajorVersion() >= 12)) { // or at least AzureDB v12 + // SQL Server 2005 or later + // https://technet.microsoft.com/en-us/library/ms177541.aspx + sql = sql.replace("{REMARKS_COLUMN_PLACEHOLDER}", "CAST([ep].[value] AS [nvarchar](MAX)) AS [REMARKS], "); + sql = sql.replace("{REMARKS_JOIN_PLACEHOLDER}", "left outer join " + databasePrefix + "[sys].[extended_properties] AS [ep] ON [ep].[class] = 1 " + + "AND [ep].[major_id] = c.object_id " + + "AND [ep].[minor_id] = column_id " + + "AND [ep].[name] = 'MS_Description' "); + } else { + sql = sql.replace("{REMARKS_COLUMN_PLACEHOLDER}", ""); + sql = sql.replace("{REMARKS_JOIN_PLACEHOLDER}", ""); + } + + List rows = this.executeAndExtract(sql, database); + + for (CachedRow row : rows) { + String typeName = row.getString("TYPE_NAME"); + if ("nvarchar".equals(typeName) || "nchar".equals(typeName)) { + Integer size = row.getInt("COLUMN_SIZE"); + if (size > 0) { + row.set("COLUMN_SIZE", size / 2); + } + } else if ((row.getInt("DATA_PRECISION") != null) && (row.getInt("DATA_PRECISION") > 0)) { + row.set("COLUMN_SIZE", row.getInt("DATA_PRECISION")); + } + } + + return rows; + } + + @Override + protected List extract(ResultSet resultSet, boolean informixIndexTrimHint) throws SQLException { + List rows = super.extract(resultSet, informixIndexTrimHint); + if ((database instanceof MSSQLDatabase) && !userDefinedTypes.isEmpty()) { //UDT types in MSSQL don't take parameters + for (CachedRow row : rows) { + String dataType = (String) row.get("TYPE_NAME"); + if (userDefinedTypes.contains(dataType.toLowerCase())) { + row.set("COLUMN_SIZE", null); + row.set("DECIMAL_DIGITS ", null); + } + } + } + return rows; + } + } + + private class ForeignKeysResultSetCache extends ResultSetCache.UnionResultSetExtractor { + final String catalogName; + final String schemaName; + final String tableName; + final String fkName; + + private ForeignKeysResultSetCache(Database database, String catalogName, String schemaName, String tableName, String fkName) { + super(database); + this.catalogName = catalogName; + this.schemaName = schemaName; + this.tableName = tableName; + this.fkName = fkName; + } + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(row.getString("FKTABLE_CAT"), row.getString("FKTABLE_SCHEM"), database, row.getString("FKTABLE_NAME"), row.getString("FK_NAME")); + } + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, tableName, fkName); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + return database instanceof OracleDatabase; + } + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("FKTABLE_SCHEM"); + } + + @Override + public List fastFetch() throws SQLException, DatabaseException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + String jdbcCatalogName = ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema); + String jdbcSchemaName = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + + if (database instanceof DB2Database) { + if (database.getDatabaseProductName().startsWith("DB2 UDB for AS/400")) { + return executeAndExtract(getDB2ForAs400Sql(jdbcSchemaName, tableName), database); + } + return querytDB2Luw(jdbcSchemaName, tableName); + } else if (database instanceof Db2zDatabase) { + return queryDb2Zos(catalogAndSchema, tableName); + } else { + List tables = new ArrayList<>(); + if (tableName == null) { + for (CachedRow row : getTables(jdbcCatalogName, jdbcSchemaName, null)) { + tables.add(row.getString("TABLE_NAME")); + } + } else { + tables.add(tableName); + } + + List returnList = new ArrayList<>(); + for (String foundTable : tables) { + if (database instanceof OracleDatabase) { + throw new RuntimeException("Should have bulk selected"); + } else { + returnList.addAll(extract(databaseMetaData.getImportedKeys(jdbcCatalogName, jdbcSchemaName, foundTable))); + } + } + + return returnList; + } + } + + @Override + public List bulkFetch() throws SQLException, DatabaseException { + if (database instanceof OracleDatabase) { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + String jdbcSchemaName = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + String sql = getOracleSql(jdbcSchemaName); + return executeAndExtract(sql, database); + } else if (database instanceof DB2Database) { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + String jdbcSchemaName = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + if (database.getDatabaseProductName().startsWith("DB2 UDB for AS/400")) { + return executeAndExtract(getDB2ForAs400Sql(jdbcSchemaName, null), database); + } + return querytDB2Luw(jdbcSchemaName, null); + } else if (database instanceof Db2zDatabase) { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + return queryDb2Zos(catalogAndSchema, null); + } else if (database instanceof MSSQLDatabase) { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + String jdbcSchemaName = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + String sql = getMSSQLSql(jdbcSchemaName, tableName); + return executeAndExtract(sql, database); + } else { + throw new RuntimeException("Cannot bulk select"); + } + } + + protected String getOracleSql(String jdbcSchemaName) { + String sql = "SELECT /*+rule*/" + + " NULL AS pktable_cat, " + + " p.owner as pktable_schem, " + + " p.table_name as pktable_name, " + + " pc.column_name as pkcolumn_name, " + + " NULL as fktable_cat, " + + " f.owner as fktable_schem, " + + " f.table_name as fktable_name, " + + " fc.column_name as fkcolumn_name, " + + " fc.position as key_seq, " + + " NULL as update_rule, " + + " decode (f.delete_rule, 'CASCADE', 0, 'SET NULL', 2, 1) as delete_rule, " + + " f.constraint_name as fk_name, " + + " p.constraint_name as pk_name, " + + " decode(f.deferrable, 'DEFERRABLE', 5, 'NOT DEFERRABLE', 7, 'DEFERRED', 6) deferrability, " + + " f.validated as fk_validate " + + "FROM " + + "all_cons_columns pc " + + "INNER JOIN all_constraints p " + + "ON pc.owner = p.owner " + + "AND pc.constraint_name = p.constraint_name " + + "INNER JOIN all_constraints f " + + "ON pc.owner = f.r_owner " + + "AND pc.constraint_name = f.r_constraint_name " + + "INNER JOIN all_cons_columns fc " + + "ON fc.owner = f.owner " + + "AND fc.constraint_name = f.constraint_name " + + "AND fc.position = pc.position "; + if (getAllCatalogsStringScratchData() == null) { + sql += "WHERE f.owner = '" + jdbcSchemaName + "' "; + } else { + sql += "WHERE f.owner IN ('" + jdbcSchemaName + "', " + getAllCatalogsStringScratchData() + ") "; + } + sql += "AND p.constraint_type in ('P', 'U') " + + "AND f.constraint_type = 'R' " + + "AND p.table_name NOT LIKE 'BIN$%' " + + "ORDER BY fktable_schem, fktable_name, key_seq"; + return sql; + } + + protected String getMSSQLSql(String jdbcSchemaName, String tableName) { + //comes from select object_definition(object_id('sp_fkeys')) + return "select " + + "convert(sysname,db_name()) AS PKTABLE_CAT, " + + "convert(sysname,schema_name(o1.schema_id)) AS PKTABLE_SCHEM, " + + "convert(sysname,o1.name) AS PKTABLE_NAME, " + + "convert(sysname,c1.name) AS PKCOLUMN_NAME, " + + "convert(sysname,db_name()) AS FKTABLE_CAT, " + + "convert(sysname,schema_name(o2.schema_id)) AS FKTABLE_SCHEM, " + + "convert(sysname,o2.name) AS FKTABLE_NAME, " + + "convert(sysname,c2.name) AS FKCOLUMN_NAME, " + + "isnull(convert(smallint,k.constraint_column_id), convert(smallint,0)) AS KEY_SEQ, " + + "convert(smallint, case ObjectProperty(f.object_id, 'CnstIsUpdateCascade') when 1 then 0 else 1 end) AS UPDATE_RULE, " + + "convert(smallint, case ObjectProperty(f.object_id, 'CnstIsDeleteCascade') when 1 then 0 else 1 end) AS DELETE_RULE, " + + "convert(sysname,object_name(f.object_id)) AS FK_NAME, " + + "convert(sysname,i.name) AS PK_NAME, " + + "convert(smallint, 7) AS DEFERRABILITY " + + "from " + + "sys.objects o1, " + + "sys.objects o2, " + + "sys.columns c1, " + + "sys.columns c2, " + + "sys.foreign_keys f inner join " + + "sys.foreign_key_columns k on (k.constraint_object_id = f.object_id) inner join " + + "sys.indexes i on (f.referenced_object_id = i.object_id and f.key_index_id = i.index_id) " + + "where " + + "o1.object_id = f.referenced_object_id and " + + "o2.object_id = f.parent_object_id and " + + "c1.object_id = f.referenced_object_id and " + + "c2.object_id = f.parent_object_id and " + + "c1.column_id = k.referenced_column_id and " + + "c2.column_id = k.parent_column_id and " + + "((object_schema_name(o1.object_id)='" + jdbcSchemaName + "'" + + " and convert(sysname,schema_name(o2.schema_id))='" + jdbcSchemaName + "' and " + + "convert(sysname,o2.name)='" + tableName + "' ) or ( convert(sysname,schema_name" + + "(o2.schema_id))='" + jdbcSchemaName + "' and convert(sysname,o2.name)='" + tableName + + "' )) order by 5, 6, 7, 9, 8"; + } + + private List querytDB2Luw(String jdbcSchemaName, String tableName) throws DatabaseException, SQLException { + List parameters = new ArrayList<>(2); + StringBuilder sql = new StringBuilder ("SELECT " + + " pk_col.tabschema AS pktable_cat, " + + " pk_col.tabname as pktable_name, " + + " pk_col.colname as pkcolumn_name, " + + " fk_col.tabschema as fktable_cat, " + + " fk_col.tabname as fktable_name, " + + " fk_col.colname as fkcolumn_name, " + + " fk_col.colseq as key_seq, " + + " decode (ref.updaterule, 'A', 3, 'R', 1, 1) as update_rule, " + + " decode (ref.deleterule, 'A', 3, 'C', 0, 'N', 2, 'R', 1, 1) as delete_rule, " + + " ref.constname as fk_name, " + + " ref.refkeyname as pk_name, " + + " 7 as deferrability " + + "FROM " + + "syscat.references ref " + + "join syscat.keycoluse fk_col on ref.constname=fk_col.constname and ref.tabschema=fk_col.tabschema and ref.tabname=fk_col.tabname " + + "join syscat.keycoluse pk_col on ref.refkeyname=pk_col.constname and ref.reftabschema=pk_col.tabschema and ref.reftabname=pk_col.tabname and pk_col.colseq=fk_col.colseq " + + "WHERE ref.tabschema = ? "); + parameters.add(jdbcSchemaName); + if (tableName != null) { + sql.append("and fk_col.tabname = ? "); + parameters.add(tableName); + } + sql.append("ORDER BY fk_col.colseq"); + return executeAndExtract(database, sql.toString(), parameters.toArray()); + } + + private String getDB2ForAs400Sql(String jdbcSchemaName, String tableName) { + return "SELECT " + + "pktable_cat, " + + "pktable_name, " + + "pkcolumn_name, " + + "fktable_cat, " + + "fktable_name, " + + "fkcolumn_name, " + + "key_seq, " + + "update_rule, " + + "delete_rule, " + + "fk_name, " + + "pk_name, " + + "deferrability " + + "FROM " + + "sysibm.SQLFORKEYS " + + "WHERE " + + "FKTABLE_SCHEM = '" + jdbcSchemaName + "' " + + "AND FKTABLE_NAME = '" + tableName + "'"; + } + + protected List queryDb2Zos(CatalogAndSchema catalogAndSchema, String tableName) throws DatabaseException, SQLException { + + List parameters = new ArrayList<>(2); + StringBuilder sql = new StringBuilder("SELECT " + + " ref.REFTBCREATOR AS pktable_cat, " + + " ref.REFTBNAME as pktable_name, " + + " pk_col.colname as pkcolumn_name, " + + " ref.CREATOR as fktable_cat, " + + " ref.TBNAME as fktable_name, " + + " fk_col.colname as fkcolumn_name, " + + " fk_col.colseq as key_seq, " + + " decode (ref.deleterule, 'A', 3, 'C', 0, 'N', 2, 'R', 1, 1) as delete_rule, " + + " ref.relname as fk_name, " + + " pk_col.colname as pk_name, " + + " 7 as deferrability " + + "FROM " + + "SYSIBM.SYSRELS ref " + + "join SYSIBM.SYSFOREIGNKEYS fk_col " + + "on ref.relname = fk_col.RELNAME " + + "and ref.CREATOR = fk_col.CREATOR " + + "and ref.TBNAME = fk_col.TBNAME " + + "join SYSIBM.SYSKEYCOLUSE pk_col " + + "on ref.REFTBCREATOR = pk_col.TBCREATOR " + + "and ref.REFTBNAME = pk_col.TBNAME " + + "and pk_col.colseq=fk_col.colseq " + + "WHERE ref.CREATOR = ? "); + parameters.add(((AbstractJdbcDatabase) CachingDatabaseMetaData.this.database).getJdbcSchemaName(catalogAndSchema)); + if (tableName != null) { + sql.append("AND ref.TBNAME = ? "); + parameters.add(tableName); + } + sql.append("ORDER BY fk_col.colseq"); + + return executeAndExtract(CachingDatabaseMetaData.this.database, sql.toString(), parameters.toArray()); + } + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + if (database instanceof AbstractDb2Database || database instanceof MSSQLDatabase) { + return super.shouldBulkSelect(schemaKey, resultSetCache); //can bulk and fast fetch + } else { + return database instanceof OracleDatabase; //oracle is slow, always bulk select while you are at it. Other databases need to go through all tables. + } + } + } + + private class GetNotNullConstraintsResultSetCache extends ResultSetCache.SingleResultSetExtractor { + final String catalogName; + final String schemaName; + final String tableName; + + private GetNotNullConstraintsResultSetCache(Database database, String catalogName, String schemaName, String tableName) { + super(database); + this.catalogName = catalogName; + this.schemaName = schemaName; + this.tableName = tableName; + } + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(row.getString("TABLE_CAT"), row.getString("TABLE_SCHEMA"), + database, row.getString("TABLE_NAME")); + } + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, tableName); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + return database instanceof OracleDatabase; + } + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("TABLE_SCHEMA"); + } + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + LiquibaseTableNamesFactory liquibaseTableNamesFactory = Scope.getCurrentScope().getSingleton(LiquibaseTableNamesFactory.class); + List liquibaseTableNames = liquibaseTableNamesFactory.getLiquibaseTableNames(database); + return liquibaseTableNames.stream().noneMatch(tableName::equalsIgnoreCase); + } + + @Override + public List fastFetchQuery() throws SQLException, DatabaseException { + if (database instanceof OracleDatabase) { + return oracleQuery(false); + } + return Collections.emptyList(); + } + + @Override + public List bulkFetchQuery() throws SQLException, DatabaseException { + if (database instanceof OracleDatabase) { + return oracleQuery(true); + } + return Collections.emptyList(); + } + + private List oracleQuery(boolean bulk) throws DatabaseException, SQLException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + String jdbcSchemaName = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + String jdbcTableName = database.escapeStringForDatabase(tableName); + String sqlToSelectNotNullConstraints = "SELECT NULL AS TABLE_CAT, atc.OWNER AS TABLE_SCHEMA, atc.OWNER, atc.TABLE_NAME, " + + "atc.COLUMN_NAME, NULLABLE, ac.VALIDATED as VALIDATED, ac.SEARCH_CONDITION, ac.CONSTRAINT_NAME " + + "FROM ALL_TAB_COLS atc " + + "JOIN all_cons_columns acc ON atc.OWNER = acc.OWNER AND atc.TABLE_NAME = acc.TABLE_NAME AND atc.COLUMN_NAME = acc.COLUMN_NAME " + + "JOIN all_constraints ac ON atc.OWNER = ac.OWNER AND atc.TABLE_NAME = ac.TABLE_NAME AND acc.CONSTRAINT_NAME = ac.CONSTRAINT_NAME "; + + if (!bulk || getAllCatalogsStringScratchData() == null) { + sqlToSelectNotNullConstraints += " WHERE atc.OWNER='" + jdbcSchemaName + "' AND atc.hidden_column='NO' AND ac.CONSTRAINT_TYPE='C' and ac.search_condition is not null "; + } else { + sqlToSelectNotNullConstraints += " WHERE atc.OWNER IN ('" + jdbcSchemaName + "', " + getAllCatalogsStringScratchData() + ") " + + " AND atc.hidden_column='NO' AND ac.CONSTRAINT_TYPE='C' and ac.search_condition is not null "; + } + + sqlToSelectNotNullConstraints += (!bulk && tableName != null && !tableName.isEmpty()) ? " AND atc.TABLE_NAME='" + jdbcTableName + "'" : ""; + + return this.executeAndExtract(sqlToSelectNotNullConstraints, database); + } + + @Override + protected List extract(ResultSet resultSet, boolean informixIndexTrimHint) throws SQLException { + List cachedRowList = new ArrayList<>(); + if (!(database instanceof OracleDatabase)) { + return cachedRowList; + } + + resultSet.setFetchSize(database.getFetchSize()); + + try { + List result = (List) new RowMapperNotNullConstraintsResultSetExtractor(new ColumnMapRowMapper(database.isCaseSensitive()) { + @Override + protected Object getColumnValue(ResultSet rs, int index) throws SQLException { + Object value = super.getColumnValue(rs, index); + if (!(value instanceof String)) { + return value; + } + return value.toString().trim(); + } + }).extractData(resultSet); + + for (Map row : result) { + cachedRowList.add(new CachedRow(row)); + } + } finally { + JdbcUtil.closeResultSet(resultSet); + } + return cachedRowList; + + } + } + + public List getTables(final String catalogName, final String schemaName, final String table) throws DatabaseException { + return getResultSetCache("getTables").get(new ResultSetCache.SingleResultSetExtractor(database) { + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + return table == null || getAllCatalogsStringScratchData() != null || super.shouldBulkSelect(schemaKey, resultSetCache); + } + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(row.getString("TABLE_CAT"), row.getString("TABLE_SCHEM"), database, row.getString("TABLE_NAME")); + } + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, table); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + return database instanceof OracleDatabase; + } + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("TABLE_SCHEM"); + } + + @Override + public List fastFetchQuery() throws SQLException, DatabaseException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + if (database instanceof OracleDatabase) { + return queryOracle(catalogAndSchema, table); + } else if (database instanceof MSSQLDatabase) { + return queryMssql(catalogAndSchema, table); + } else if (database instanceof Db2zDatabase) { + return queryDb2Zos(catalogAndSchema, table); + } else if (database instanceof PostgresDatabase) { + return queryPostgres(catalogAndSchema, table); + } + + String catalog = ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema); + String schema = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + return extract(databaseMetaData.getTables(catalog, escapeForLike(schema, database), ((table == null) ? + SQL_FILTER_MATCH_ALL : escapeForLike(table, database)), new String[]{"TABLE"})); + } + + @Override + public List bulkFetchQuery() throws SQLException, DatabaseException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + if (database instanceof OracleDatabase) { + return queryOracle(catalogAndSchema, null); + } else if (database instanceof MSSQLDatabase) { + return queryMssql(catalogAndSchema, null); + } else if (database instanceof Db2zDatabase) { + return queryDb2Zos(catalogAndSchema, null); + } else if (database instanceof PostgresDatabase) { + return queryPostgres(catalogAndSchema, table); + } + + String catalog = ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema); + String schema = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + return extract(databaseMetaData.getTables(catalog, escapeForLike(schema, database), SQL_FILTER_MATCH_ALL, new String[]{"TABLE"})); + } + + private List queryMssql(CatalogAndSchema catalogAndSchema, String tableName) throws DatabaseException, SQLException { + String ownerName = database.correctObjectName(catalogAndSchema.getSchemaName(), Schema.class); + + String databaseName = StringUtil.trimToNull(database.correctObjectName(catalogAndSchema.getCatalogName(), Catalog.class)); + String dbIdParam; + String databasePrefix; + if (databaseName == null) { + databasePrefix = ""; + dbIdParam = ""; + } else { + dbIdParam = ", db_id('" + databaseName + "')"; + databasePrefix = "[" + databaseName + "]."; + } + + + //From select object_definition(object_id('sp_tables')) + String sql = "select " + + "db_name(" + (databaseName == null ? "" : "db_id('" + databaseName + "')") + ") AS TABLE_CAT, " + + "convert(sysname,object_schema_name(o.object_id" + dbIdParam + ")) AS TABLE_SCHEM, " + + "convert(sysname,o.name) AS TABLE_NAME, " + + "'TABLE' AS TABLE_TYPE, " + + "CAST(ep.value as varchar(max)) as REMARKS " + + "from " + databasePrefix + "sys.all_objects o " + + "left outer join sys.extended_properties ep on ep.name='MS_Description' and major_id=o.object_id and minor_id=0 " + + "where " + + "o.type in ('U') " + + "and has_perms_by_name(" + (databaseName == null ? "" : "quotename('" + databaseName + "') + '.' + ") + "quotename(object_schema_name(o.object_id" + dbIdParam + ")) + '.' + quotename(o.name), 'object', 'select') = 1 " + + "and charindex(substring(o.type,1,1),'U') <> 0 " + + "and object_schema_name(o.object_id" + dbIdParam + ")='" + database.escapeStringForDatabase(ownerName) + "'"; + if (tableName != null) { + sql += " AND o.name='" + database.escapeStringForDatabase(tableName) + "' "; + } + sql += "order by 4, 1, 2, 3"; + + return executeAndExtract(sql, database); + } + + private List queryOracle(CatalogAndSchema catalogAndSchema, String tableName) throws DatabaseException, SQLException { + String ownerName = database.correctObjectName(catalogAndSchema.getCatalogName(), Schema.class); + + String sql = "SELECT null as TABLE_CAT, a.OWNER as TABLE_SCHEM, a.TABLE_NAME as TABLE_NAME, " + + "a.TEMPORARY as TEMPORARY, a.DURATION as DURATION, 'TABLE' as TABLE_TYPE, " + + "c.COMMENTS as REMARKS, A.tablespace_name as tablespace_name, CASE WHEN A.tablespace_name = " + + "(SELECT DEFAULT_TABLESPACE FROM USER_USERS) THEN 'true' ELSE null END as default_tablespace " + + "from ALL_TABLES a " + + "join ALL_TAB_COMMENTS c on a.TABLE_NAME=c.table_name and a.owner=c.owner " + + "left outer join ALL_QUEUE_TABLES q ON a.TABLE_NAME = q.QUEUE_TABLE and a.OWNER = q.OWNER " + + "WHERE q.QUEUE_TABLE is null "; + String allCatalogsString = getAllCatalogsStringScratchData(); + if (tableName != null || allCatalogsString == null) { + sql += "AND a.OWNER='" + ownerName + "'"; + } else { + sql += "AND a.OWNER IN ('" + ownerName + "', " + allCatalogsString + ")"; + } + if (tableName != null) { + sql += " AND a.TABLE_NAME='" + tableName + "'"; + } + + return executeAndExtract(sql, database); + } + + private List queryDb2Zos(CatalogAndSchema catalogAndSchema, String tableName) throws DatabaseException, SQLException { + String ownerName = database.correctObjectName(catalogAndSchema.getCatalogName(), Schema.class); + + String sql = "SELECT CREATOR AS TABLE_SCHEM, " + + "NAME AS TABLE_NAME, " + + "'TABLE' AS TABLE_TYPE, " + + "REMARKS " + + "FROM SYSIBM.SYSTABLES " + + "WHERE TYPE = 'T'"; + List parameters = new ArrayList<>(2); + if (ownerName != null) { + sql += " AND CREATOR = ?"; + parameters.add(ownerName); + } + if (tableName != null) { + sql += " AND NAME = ?"; + parameters.add(tableName); + } + + return executeAndExtract(database, sql, parameters.toArray()); + } + + private List queryPostgres(CatalogAndSchema catalogAndSchema, String tableName) throws SQLException { + String catalog = ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema); + String schema = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + return extract(databaseMetaData.getTables(catalog, escapeForLike(schema, database), ((tableName == null) ? + SQL_FILTER_MATCH_ALL : escapeForLike(tableName, database)), new String[]{"TABLE", "PARTITIONED TABLE"})); + + } + }); + } + + public List getViews(final String catalogName, final String schemaName, String viewName) throws DatabaseException { + final String view; + if (database instanceof DB2Database) { + view = database.correctObjectName(viewName, View.class); + } else { + view = viewName; + } + return getResultSetCache("getViews").get(new ResultSetCache.SingleResultSetExtractor(database) { + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + return view == null || getAllCatalogsStringScratchData() != null || super.shouldBulkSelect(schemaKey, resultSetCache); + } + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(row.getString("TABLE_CAT"), row.getString("TABLE_SCHEM"), database, row.getString("TABLE_NAME")); + } + + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, view); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + return database instanceof OracleDatabase; + } + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("TABLE_SCHEM"); + } + + + @Override + public List fastFetchQuery() throws SQLException, DatabaseException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + if (database instanceof OracleDatabase) { + return queryOracle(catalogAndSchema, view); + } else if (database instanceof MSSQLDatabase) { + return queryMssql(catalogAndSchema, view); + } + + String catalog = ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema); + String schema = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + return extract(databaseMetaData.getTables(catalog, escapeForLike(schema, database), ((view == null) ? SQL_FILTER_MATCH_ALL + : escapeForLike(view, database)), new String[]{"VIEW"})); + } + + @Override + public List bulkFetchQuery() throws SQLException, DatabaseException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + if (database instanceof OracleDatabase) { + return queryOracle(catalogAndSchema, null); + } else if (database instanceof MSSQLDatabase) { + return queryMssql(catalogAndSchema, null); + } + + String catalog = ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema); + String schema = ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema); + return extract(databaseMetaData.getTables(catalog, escapeForLike(schema, database), SQL_FILTER_MATCH_ALL, new String[]{"VIEW"})); + } + + private List queryMssql(CatalogAndSchema catalogAndSchema, String viewName) throws DatabaseException, SQLException { + String ownerName = database.correctObjectName(catalogAndSchema.getSchemaName(), Schema.class); + String databaseName = StringUtil.trimToNull(database.correctObjectName(catalogAndSchema.getCatalogName(), Catalog.class)); + String dbIdParam = ""; + String databasePrefix = ""; + boolean haveDatabaseName = databaseName != null; + + if (haveDatabaseName) { + dbIdParam = ", db_id('" + databaseName + "')"; + databasePrefix = "[" + databaseName + "]."; + } + String tableCatParam = haveDatabaseName ? "db_id('" + databaseName + "')" : ""; + String permsParam = haveDatabaseName ? "quotename('" + databaseName + "') + '.' + " : ""; + + String sql = "select " + + "db_name(" + tableCatParam + ") AS TABLE_CAT, " + + "convert(sysname,object_schema_name(o.object_id" + dbIdParam + ")) AS TABLE_SCHEM, " + + "convert(sysname,o.name) AS TABLE_NAME, " + + "'VIEW' AS TABLE_TYPE, " + + "CAST(ep.value as varchar(max)) as REMARKS " + + "from " + databasePrefix + "sys.all_objects o " + + "left join sys.extended_properties ep on ep.name='MS_Description' and major_id=o.object_id and minor_id=0 " + + "where " + + "o.type in ('V') " + + "and has_perms_by_name(" + permsParam + "quotename(object_schema_name(o.object_id" + dbIdParam + ")) + '.' + quotename(o.name), 'object', 'select') = 1 " + + "and charindex(substring(o.type,1,1),'V') <> 0 " + + "and object_schema_name(o.object_id" + dbIdParam + ")='" + database.escapeStringForDatabase(ownerName) + "'"; + if (viewName != null) { + sql += " AND o.name='" + database.escapeStringForDatabase(viewName) + "' "; + } + sql += "order by 4, 1, 2, 3"; + + return executeAndExtract(sql, database); + } + + + private List queryOracle(CatalogAndSchema catalogAndSchema, String viewName) throws DatabaseException, SQLException { + String ownerName = database.correctObjectName(catalogAndSchema.getCatalogName(), Schema.class); + + String sql = "SELECT null as TABLE_CAT, a.OWNER as TABLE_SCHEM, a.VIEW_NAME as TABLE_NAME, 'TABLE' as TABLE_TYPE, c.COMMENTS as REMARKS, TEXT as OBJECT_BODY"; + if (database.getDatabaseMajorVersion() > 10) { + sql += ", EDITIONING_VIEW"; + } + sql += " from ALL_VIEWS a " + + "join ALL_TAB_COMMENTS c on a.VIEW_NAME=c.table_name and a.owner=c.owner "; + if (viewName != null || getAllCatalogsStringScratchData() == null) { + sql += "WHERE a.OWNER='" + ownerName + "'"; + } else { + sql += "WHERE a.OWNER IN ('" + ownerName + "', " + getAllCatalogsStringScratchData() + ")"; + } + if (viewName != null) { + sql += " AND a.VIEW_NAME='" + database.correctObjectName(viewName, View.class) + "'"; + } + sql += " AND a.VIEW_NAME not in (select mv.name from all_registered_mviews mv where mv.owner=a.owner)"; + + return executeAndExtract(sql, database); + } + }); + } + + public List getPrimaryKeys(final String catalogName, final String schemaName, final String table) throws DatabaseException { + return getResultSetCache("getPrimaryKeys").get(new ResultSetCache.SingleResultSetExtractor(database) { + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(row.getString("TABLE_CAT"), row.getString("TABLE_SCHEM"), database, row.getString("TABLE_NAME")); + } + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, table); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + return database instanceof OracleDatabase; + } + + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("TABLE_SCHEM"); + } + + @Override + public List fastFetchQuery() throws SQLException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + try { + List foundPks = new ArrayList<>(); + if (table == null) { + List tables = CachingDatabaseMetaData.this.getTables(catalogName, schemaName, null); + for (CachedRow table : tables) { + List pkInfo = getPkInfo(catalogAndSchema, table.getString("TABLE_NAME")); + if (pkInfo != null) { + foundPks.addAll(pkInfo); + } + } + return foundPks; + } else { + List pkInfo = getPkInfo(catalogAndSchema, table); + if (pkInfo != null) { + foundPks.addAll(pkInfo); + } + } + return foundPks; + } catch (DatabaseException e) { + throw new SQLException(e); + } + } + + private List getPkInfo(CatalogAndSchema catalogAndSchema, String tableName) throws DatabaseException, SQLException { + List pkInfo; + if (database instanceof MSSQLDatabase) { + String sql = mssqlSql(catalogAndSchema, tableName); + pkInfo = executeAndExtract(sql, database); + } else { + if (database instanceof Db2zDatabase) { + String sql = "SELECT 'NULL' AS TABLE_CAT," + + " SYSTAB.TBCREATOR AS TABLE_SCHEM, " + + "SYSTAB.TBNAME AS TABLE_NAME, " + + "COLUSE.COLNAME AS COLUMN_NAME, " + + "COLUSE.COLSEQ AS KEY_SEQ, " + + "SYSTAB.CONSTNAME AS PK_NAME " + + "FROM SYSIBM.SYSTABCONST SYSTAB " + + "JOIN SYSIBM.SYSKEYCOLUSE COLUSE " + + "ON SYSTAB.TBCREATOR = COLUSE.TBCREATOR " + + "WHERE SYSTAB.TYPE = 'P' " + + "AND SYSTAB.TBNAME = ? " + + "AND SYSTAB.TBCREATOR = ? " + + "AND SYSTAB.TBNAME=COLUSE.TBNAME " + + "AND SYSTAB.CONSTNAME=COLUSE.CONSTNAME " + + "ORDER BY COLUSE.COLNAME"; + try { + return executeAndExtract(database, sql, table, ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema)); + } catch (DatabaseException e) { + throw new SQLException(e); + } + } else if (database instanceof OracleDatabase) { + warnAboutDbaRecycleBin(); + + String sql = "SELECT NULL AS table_cat, c.owner AS table_schem, c.table_name, c.column_name as COLUMN_NAME, c.position AS key_seq, c.constraint_name AS pk_name, k.VALIDATED as VALIDATED " + + "FROM all_cons_columns c, all_constraints k " + + "LEFT JOIN " + (((OracleDatabase) database).canAccessDbaRecycleBin() ? "dba_recyclebin" : "user_recyclebin") + " d ON d.object_name=k.table_name " + + "WHERE k.constraint_type = 'P' " + + "AND d.object_name IS NULL " + + "AND k.table_name = '" + table + "' " + + "AND k.owner = '" + ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema) + "' " + + "AND k.constraint_name = c.constraint_name " + + "AND k.table_name = c.table_name " + + "AND k.owner = c.owner " + + "ORDER BY column_name"; + try { + return executeAndExtract(sql, database); + } catch (DatabaseException e) { + throw new SQLException(e); + } + } else if (database instanceof CockroachDatabase) { + // This is the same as the query generated by PGJDBC's getPrimaryKeys method, except it + // also adds an `asc_or_desc` column to the result. + String sql = "SELECT " + + " result.table_cat, " + + " result.table_schem, " + + " result.table_name, " + + " result.column_name, " + + " result.key_seq, " + + " result.pk_name, " + + " CASE result.indoption[result.key_seq - 1] & 1 " + + " WHEN 1 THEN 'D' " + + " ELSE 'A' " + + " END AS asc_or_desc " + + "FROM " + + " (" + + " SELECT " + + " NULL AS table_cat, " + + " n.nspname AS table_schem, " + + " ct.relname AS table_name, " + + " a.attname AS column_name, " + + " (information_schema._pg_expandarray(i.indkey)).n " + + " AS key_seq, " + + " ci.relname AS pk_name, " + + " information_schema._pg_expandarray(i.indkey) AS keys, " + + " i.indoption, " + + " a.attnum AS a_attnum " + + " FROM " + + " pg_catalog.pg_class AS ct " + + " JOIN pg_catalog.pg_attribute AS a ON (ct.oid = a.attrelid) " + + " JOIN pg_catalog.pg_namespace AS n ON " + + " (ct.relnamespace = n.oid) " + + " JOIN pg_catalog.pg_index AS i ON (a.attrelid = i.indrelid) " + + " JOIN pg_catalog.pg_class AS ci ON (ci.oid = i.indexrelid) " + + " WHERE " + + " true " + + " AND n.nspname = '" + ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema) + "' " + + " AND ct.relname = '" + table + "' " + + " AND i.indisprimary" + + " ) " + + " AS result " + + "WHERE " + + " result.a_attnum = (result.keys).x " + + "ORDER BY " + + " result.table_name, result.pk_name, result.key_seq"; + + try { + return executeAndExtract(sql, database); + } catch (DatabaseException e) { + throw new SQLException(e); + } + } else { + return extract( + databaseMetaData.getPrimaryKeys( + ((AbstractJdbcDatabase) database).getJdbcCatalogName(catalogAndSchema), + ((AbstractJdbcDatabase) database).getJdbcSchemaName(catalogAndSchema), + table + ) + ); + } + } + return pkInfo; + } + + private String mssqlSql(CatalogAndSchema catalogAndSchema, String tableName) throws DatabaseException { + String sql; + sql = + "SELECT " + + "DB_NAME() AS [TABLE_CAT], " + + "[s].[name] AS [TABLE_SCHEM], " + + "[t].[name] AS [TABLE_NAME], " + + "[c].[name] AS [COLUMN_NAME], " + + "CASE [ic].[is_descending_key] WHEN 0 THEN N'A' WHEN 1 THEN N'D' END AS [ASC_OR_DESC], " + + "[ic].[key_ordinal] AS [KEY_SEQ], " + + "[kc].[name] AS [PK_NAME] " + + "FROM [sys].[schemas] AS [s] " + + "INNER JOIN [sys].[tables] AS [t] " + + "ON [t].[schema_id] = [s].[schema_id] " + + "INNER JOIN [sys].[key_constraints] AS [kc] " + + "ON [kc].[parent_object_id] = [t].[object_id] " + + "INNER JOIN [sys].[indexes] AS [i] " + + "ON [i].[object_id] = [kc].[parent_object_id] " + + "AND [i].[index_id] = [kc].[unique_index_id] " + + "INNER JOIN [sys].[index_columns] AS [ic] " + + "ON [ic].[object_id] = [i].[object_id] " + + "AND [ic].[index_id] = [i].[index_id] " + + "INNER JOIN [sys].[columns] AS [c] " + + "ON [c].[object_id] = [ic].[object_id] " + + "AND [c].[column_id] = [ic].[column_id] " + + "WHERE [s].[name] = N'" + database.escapeStringForDatabase(catalogAndSchema.getSchemaName()) + "' " + // The schema name was corrected in the customized CatalogAndSchema + (tableName == null ? "" : "AND [t].[name] = N'" + database.escapeStringForDatabase(database.correctObjectName(tableName, Table.class)) + "' ") + + "AND [kc].[type] = 'PK' " + + "AND [ic].[key_ordinal] > 0 " + + "ORDER BY " + + "[ic].[key_ordinal]"; + return sql; + } + + @Override + public List bulkFetchQuery() throws SQLException { + if (database instanceof OracleDatabase) { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + warnAboutDbaRecycleBin(); + try { + String sql = "SELECT NULL AS table_cat, c.owner AS table_schem, c.table_name, c.column_name, c.position AS key_seq,c.constraint_name AS pk_name, k.VALIDATED as VALIDATED FROM " + + "all_cons_columns c, " + + "all_constraints k " + + "LEFT JOIN " + (((OracleDatabase) database).canAccessDbaRecycleBin() ? "dba_recyclebin" : "user_recyclebin") + " d ON d.object_name=k.table_name " + + "WHERE k.constraint_type = 'P' " + + "AND d.object_name IS NULL "; + if (getAllCatalogsStringScratchData() == null) { + sql += "AND k.owner='" + catalogAndSchema.getCatalogName() + "' "; + } else { + sql += "AND k.owner IN ('" + catalogAndSchema.getCatalogName() + "', " + getAllCatalogsStringScratchData() + ")"; + } + sql += "AND k.constraint_name = c.constraint_name " + + "AND k.table_name = c.table_name " + + "AND k.owner = c.owner " + + "ORDER BY column_name"; + return executeAndExtract(sql, database); + } catch (DatabaseException e) { + throw new SQLException(e); + } + } else if (database instanceof MSSQLDatabase) { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + try { + return executeAndExtract(mssqlSql(catalogAndSchema, null), database); + } catch (DatabaseException e) { + throw new SQLException(e); + } + } + return null; + } + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + if ((database instanceof OracleDatabase) || (database instanceof MSSQLDatabase)) { + return table == null || getAllCatalogsStringScratchData() != null || super.shouldBulkSelect(schemaKey, resultSetCache); + } else { + return false; + } + } + }); + } + + public List getUniqueConstraints(final String catalogName, final String schemaName, final String tableName) throws DatabaseException { + return getResultSetCache("getUniqueConstraints").get(new ResultSetCache.SingleResultSetExtractor(database) { + + @Override + protected boolean shouldBulkSelect(String schemaKey, ResultSetCache resultSetCache) { + return tableName == null || getAllCatalogsStringScratchData() != null || super.shouldBulkSelect(schemaKey, resultSetCache); + } + + @Override + public boolean bulkContainsSchema(String schemaKey) { + return database instanceof OracleDatabase; + } + + @Override + public String getSchemaKey(CachedRow row) { + return row.getString("CONSTRAINT_SCHEM"); + } + + @Override + public ResultSetCache.RowData rowKeyParameters(CachedRow row) { + return new ResultSetCache.RowData(catalogName, schemaName, database, row.getString("TABLE_NAME")); + } + + @Override + public ResultSetCache.RowData wantedKeyParameters() { + return new ResultSetCache.RowData(catalogName, schemaName, database, tableName); + } + + @Override + public List fastFetchQuery() throws SQLException, DatabaseException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + return queryDb(catalogAndSchema, tableName); + } + + @Override + public List bulkFetchQuery() throws SQLException, DatabaseException { + CatalogAndSchema catalogAndSchema = new CatalogAndSchema(catalogName, schemaName).customize(database); + + return queryDb(catalogAndSchema, null); + } + + private List queryDb(CatalogAndSchema catalogAndSchema, String tableName) throws SQLException, DatabaseException { + + String jdbcCatalogName = catalogAndSchema.getCatalogName(); + String jdbcSchemaName = catalogAndSchema.getSchemaName(); + + Database database = getDatabase(); + List parameters = new ArrayList<>(3); + String sql = null; + if (database instanceof Ingres9Database) { + sql = "select CONSTRAINT_NAME, TABLE_NAME from iiconstraints where schema_name ='" + + schemaName + "' and constraint_type='U'"; + if (tableName != null) { + sql += " and table_name='" + tableName + "'"; + } + } else if ((database instanceof MySQLDatabase) || (database instanceof HsqlDatabase) || (database + instanceof MariaDBDatabase)) { + sql = "select CONSTRAINT_NAME, TABLE_NAME " + + "from " + database.getSystemSchema() + ".table_constraints " + + "where constraint_schema='" + jdbcCatalogName + "' " + + "and constraint_type='UNIQUE'"; + if (tableName != null) { + sql += " and table_name='" + tableName + "'"; + } + } else if (database instanceof PostgresDatabase) { + sql = "select CONSTRAINT_NAME, TABLE_NAME " + + "from " + database.getSystemSchema() + ".table_constraints " + + "where constraint_catalog='" + jdbcCatalogName + "' " + + "and constraint_schema='" + jdbcSchemaName + "' " + + "and constraint_type='UNIQUE'"; + if (tableName != null) { + sql += " and table_name='" + tableName + "'"; + } + } else if (database.getClass().getName().contains("MaxDB")) { //have to check classname as this is currently an extension + sql = "select distinct tablename AS TABLE_NAME, constraintname AS CONSTRAINT_NAME from CONSTRAINTCOLUMNS WHERE CONSTRAINTTYPE = 'UNIQUE_CONST'"; + if (tableName != null) { + sql += " and tablename='" + tableName + "'"; + } + } else if (database instanceof MSSQLDatabase) { + sql = + "SELECT " + + "[TC].[CONSTRAINT_NAME], " + + "[TC].[TABLE_NAME], " + + "[TC].[CONSTRAINT_CATALOG] AS INDEX_CATALOG, " + + "[TC].[CONSTRAINT_SCHEMA] AS INDEX_SCHEMA, " + + "[IDX].[TYPE_DESC], " + + "[IDX].[name] AS INDEX_NAME " + + "FROM [INFORMATION_SCHEMA].[TABLE_CONSTRAINTS] AS [TC] " + + "JOIN sys.indexes AS IDX ON IDX.name=[TC].[CONSTRAINT_NAME] AND object_schema_name(object_id)=[TC].[CONSTRAINT_SCHEMA] " + + "WHERE [TC].[CONSTRAINT_TYPE] = 'UNIQUE' " + + "AND [TC].[CONSTRAINT_CATALOG] = N'" + database.escapeStringForDatabase(jdbcCatalogName) + "' " + + "AND [TC].[CONSTRAINT_SCHEMA] = N'" + database.escapeStringForDatabase(jdbcSchemaName) + "'"; + if (tableName != null) { + sql += " AND [TC].[TABLE_NAME] = N'" + database.escapeStringForDatabase(database.correctObjectName(tableName, Table.class)) + "'"; + } + } else if (database instanceof OracleDatabase) { + warnAboutDbaRecycleBin(); + + sql = "select uc.owner AS CONSTRAINT_SCHEM, uc.constraint_name, uc.table_name,uc.status,uc.deferrable,uc.deferred,ui.tablespace_name, ui.index_name, ui.owner as INDEX_CATALOG, uc.VALIDATED as VALIDATED, ac.COLUMN_NAME as COLUMN_NAME " + + "from all_constraints uc " + + "join all_indexes ui on uc.index_name = ui.index_name and uc.owner=ui.table_owner and uc.table_name=ui.table_name " + + "LEFT OUTER JOIN " + (((OracleDatabase) database).canAccessDbaRecycleBin() ? "dba_recyclebin" : "user_recyclebin") + " d ON d.object_name=ui.table_name " + + "LEFT JOIN all_cons_columns ac ON ac.OWNER = uc.OWNER AND ac.TABLE_NAME = uc.TABLE_NAME AND ac.CONSTRAINT_NAME = uc.CONSTRAINT_NAME " + + "where uc.constraint_type='U' "; + if (tableName != null || getAllCatalogsStringScratchData() == null) { + sql += "and uc.owner = '" + jdbcSchemaName + "'"; + } else { + sql += "and uc.owner IN ('" + jdbcSchemaName + "', " + getAllCatalogsStringScratchData() + ")"; + } + sql += "AND d.object_name IS NULL "; + + if (tableName != null) { + sql += " and uc.table_name = '" + tableName + "'"; + } + } else if (database instanceof DB2Database) { + // if we are on DB2 AS400 iSeries + if (database.getDatabaseProductName().startsWith("DB2 UDB for AS/400")) { + sql = "select constraint_name as constraint_name, table_name as table_name from QSYS2.TABLE_CONSTRAINTS where table_schema='" + jdbcSchemaName + "' and constraint_type='UNIQUE'"; + if (tableName != null) { + sql += " and table_name = '" + tableName + "'"; + } + // DB2 z/OS + } + // here we are on DB2 UDB + else { + sql = "select distinct k.constname as constraint_name, t.tabname as TABLE_NAME " + + "from syscat.keycoluse k " + + "inner join syscat.tabconst t " + + "on k.constname = t.constname " + + "where t.tabschema = ? " + + "and t.type = 'U'"; + parameters.add(jdbcSchemaName); + if (tableName != null) { + sql += " and t.tabname = ?"; + parameters.add(tableName); + } + } + } else if (database instanceof Db2zDatabase) { + sql = "select k.constname as constraint_name, t.tbname as TABLE_NAME" + + " from SYSIBM.SYSKEYCOLUSE k" + + " inner join SYSIBM.SYSTABCONST t" + + " on k.constname = t.constname" + + " and k.TBCREATOR = t.TBCREATOR" + + " and k.TBNAME = t.TBNAME" + + " where t.TBCREATOR = ?" + + " and t.TYPE = 'U'"; + parameters.add(jdbcSchemaName); + if (tableName != null) { + sql += " and t.TBNAME = ?"; + parameters.add(tableName); + } + } else if (database instanceof FirebirdDatabase) { + sql = "SELECT TRIM(RDB$INDICES.RDB$INDEX_NAME) AS CONSTRAINT_NAME, " + + "TRIM(RDB$INDICES.RDB$RELATION_NAME) AS TABLE_NAME " + + "FROM RDB$INDICES " + + "LEFT JOIN RDB$RELATION_CONSTRAINTS " + + "ON RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME = RDB$INDICES.RDB$INDEX_NAME " + + "WHERE RDB$INDICES.RDB$UNIQUE_FLAG IS NOT NULL " + + "AND (" + + "RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE IS NULL " + + "OR TRIM(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE)='UNIQUE') " + + "AND NOT(RDB$INDICES.RDB$INDEX_NAME LIKE 'RDB$%')"; + if (tableName != null) { + sql += " AND TRIM(RDB$INDICES.RDB$RELATION_NAME)='" + tableName + "'"; + } + } else if (database instanceof DerbyDatabase) { + sql = "select c.constraintname as CONSTRAINT_NAME, tablename AS TABLE_NAME " + + "from sys.systables t, sys.sysconstraints c, sys.sysschemas s " + + "where s.schemaname='" + jdbcCatalogName + "' " + + "and t.tableid = c.tableid " + + "and t.schemaid=s.schemaid " + + "and c.type = 'U'"; + if (tableName != null) { + sql += " AND t.tablename = '" + tableName + "'"; + } + } else if (database instanceof InformixDatabase) { + sql = "select unique sysindexes.idxname as CONSTRAINT_NAME, sysindexes.idxtype, systables.tabname as TABLE_NAME " + + "from sysindexes, systables " + + "left outer join sysconstraints on sysconstraints.tabid = systables.tabid and sysconstraints.constrtype = 'P' " + + "where sysindexes.tabid = systables.tabid and sysindexes.idxtype = 'U' " + + "and sysconstraints.idxname != sysindexes.idxname " + + "and sysconstraints.tabid = sysindexes.tabid"; + if (tableName != null) { + sql += " and systables.tabname = '" + database.correctObjectName(tableName, Table.class) + "'"; + } + } else if (database instanceof SybaseDatabase) { + sql = "select idx.name as CONSTRAINT_NAME, tbl.name as TABLE_NAME " + + "from sysindexes idx " + + "inner join sysobjects tbl on tbl.id = idx.id " + + "where idx.indid between 1 and 254 " + + "and (idx.status & 2) = 2 " + + "and tbl.type = 'U'"; + if (tableName != null) { + sql += " and tbl.name = '" + database.correctObjectName(tableName, Table.class) + "'"; + } + } else if (database instanceof SybaseASADatabase) { + sql = "select sysconstraint.constraint_name, sysconstraint.constraint_type, systable.table_name " + + "from sysconstraint, systable " + + "where sysconstraint.table_object_id = systable.object_id " + + "and sysconstraint.constraint_type = 'U'"; + if (tableName != null) { + sql += " and systable.table_name = '" + tableName + "'"; + } + } else { + if (database instanceof H2Database) { + try { + if (database.getDatabaseMajorVersion() >= 2) { + sql = "select CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME " + + "from " + database.getSystemSchema() + ".table_constraints " + + "where constraint_schema='" + jdbcSchemaName + "' " + + "and constraint_catalog='" + jdbcCatalogName + "' " + + "and constraint_type='UNIQUE'"; + if (tableName != null) { + sql += " and table_name='" + tableName + "'"; + } + } + } catch (DatabaseException e) { + Scope.getCurrentScope().getLog(getClass()).fine("Cannot determine h2 version, using default unique constraint query"); + } + } + if (sql == null) { + + sql = "select CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME " + + "from " + database.getSystemSchema() + ".constraints " + + "where constraint_schema='" + jdbcSchemaName + "' " + + "and constraint_catalog='" + jdbcCatalogName + "' " + + "and constraint_type='UNIQUE'"; + if (tableName != null) { + sql += " and table_name='" + tableName + "'"; + } + } + } + + return executeAndExtract(database, database instanceof InformixDatabase, sql, parameters.toArray()); + } + }); + } + } + + private String getAllCatalogsStringScratchData() { + return (String) getScratchData(ALL_CATALOGS_STRING_SCRATCH_KEY); + } + + private String escapeForLike(String string, Database database) { + if (string == null) { + return null; + } + + if (database instanceof SQLiteDatabase || database instanceof DmDatabase) { + //sqlite jdbc's queries does not support escaped patterns. + // DM 也不支持转义的匹配方式,需要兼容 + return string; + } + + return string + .replace("%", "\\%") + .replace("_", "\\_"); + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java new file mode 100644 index 0000000..2ac83d5 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java @@ -0,0 +1,2094 @@ +/* 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.AgendaOperationExecutionListener; +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 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 defaultCommandInterceptors; + protected CommandConfig defaultCommandConfig; + protected CommandConfig schemaCommandConfig; + protected CommandContextFactory commandContextFactory; + protected CommandInterceptor commandInvoker; + + protected AgendaOperationRunner agendaOperationRunner = (commandContext, runnable) -> runnable.run(); + protected Collection agendaOperationExecutionListeners; + + protected List customPreCommandInterceptors; + protected List customPostCommandInterceptors; + protected List commandInterceptors; + + protected Map engineConfigurations = new HashMap<>(); + protected Map 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 engineLifecycleListeners; + + // Event Registry ////////////////////////////////////////////////// + protected Map 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. + *

+ * 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> customMybatisMappers; + protected Set customMybatisXMLMappers; + protected List customMybatisInterceptors; + + protected Set dependentEngineMyBatisXmlMappers; + protected List dependentEngineMybatisTypeAliasConfigs; + protected List dependentEngineMybatisTypeHandlerConfigs; + + // SESSION FACTORIES /////////////////////////////////////////////// + protected List customSessionFactories; + protected Map, SessionFactory> sessionFactories; + + protected boolean enableEventDispatcher = true; + protected FlowableEventDispatcher eventDispatcher; + protected List eventListeners; + protected Map> typedEventListeners; + protected List 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_'. + * + *

+ * 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. + */ + 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 customPreDeployers; + protected List customPostDeployers; + protected List deployers; + + // CONFIGURATORS //////////////////////////////////////////////////////////// + + protected boolean enableConfiguratorServiceLoader = true; // Enabled by default. In certain environments this should be set to false (eg osgi) + protected List configurators; // The injected configurators + protected List 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);// 加入达梦支持 可以直接使用ORACLE或者MYSQL的 + return databaseTypeMappings; + } + + protected Map 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 getDefaultCommandInterceptors() { + if (defaultCommandInterceptors == null) { + List 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 additionalCommandInterceptors = getAdditionalDefaultCommandInterceptors(); + if (additionalCommandInterceptors != null) { + interceptors.addAll(additionalCommandInterceptors); + } + + defaultCommandInterceptors = interceptors; + } + return defaultCommandInterceptors; + } + + public abstract String getEngineCfgKey(); + + public abstract String getEngineScopeType(); + + public List getAdditionalDefaultCommandInterceptors() { + return null; + } + + public void initCommandExecutor() { + if (commandExecutor == null) { + CommandInterceptor first = initInterceptorChain(commandInterceptors); + commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first); + } + } + + public CommandInterceptor initInterceptorChain(List 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> insertOrder, List> deleteOrder) { + if (insertOrder != null) { + for (Class clazz : insertOrder) { + dbSqlSessionFactory.getInsertionOrder().add(clazz); + + if (isBulkInsertEnabled) { + dbSqlSessionFactory.getBulkInserteableEntityClasses().add(clazz); + } + } + } + + if (deleteOrder != null) { + for (Class 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 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 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() { + + @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 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 getEngineLifecycleListeners() { + return engineLifecycleListeners; + } + + public AbstractEngineConfiguration setEngineLifecycleListeners(List 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 getSchemaManagementCmd() { + return schemaManagementCmd; + } + + public AbstractEngineConfiguration setSchemaManagementCmd(Command 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 getBeans() { + return beans; + } + + public AbstractEngineConfiguration setBeans(Map 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 Collection getAgendaOperationExecutionListeners() { + return agendaOperationExecutionListeners; + } + + public AbstractEngineConfiguration addAgendaOperationExecutionListener(AgendaOperationExecutionListener listener) { + if (this.agendaOperationExecutionListeners == null) { + this.agendaOperationExecutionListeners = new ArrayList<>(); + } + this.agendaOperationExecutionListeners.add(listener); + return this; + } + + public AbstractEngineConfiguration setAgendaOperationExecutionListeners(Collection agendaOperationExecutionListeners) { + this.agendaOperationExecutionListeners = agendaOperationExecutionListeners; + return this; + } + + public List getCustomPreCommandInterceptors() { + return customPreCommandInterceptors; + } + + public AbstractEngineConfiguration addCustomPreCommandInterceptor(CommandInterceptor commandInterceptor) { + if (this.customPreCommandInterceptors == null) { + this.customPreCommandInterceptors = new ArrayList<>(); + } + this.customPreCommandInterceptors.add(commandInterceptor); + return this; + } + + public AbstractEngineConfiguration setCustomPreCommandInterceptors(List customPreCommandInterceptors) { + this.customPreCommandInterceptors = customPreCommandInterceptors; + return this; + } + + public List getCustomPostCommandInterceptors() { + return customPostCommandInterceptors; + } + + public AbstractEngineConfiguration addCustomPostCommandInterceptor(CommandInterceptor commandInterceptor) { + if (this.customPostCommandInterceptors == null) { + this.customPostCommandInterceptors = new ArrayList<>(); + } + this.customPostCommandInterceptors.add(commandInterceptor); + return this; + } + + public AbstractEngineConfiguration setCustomPostCommandInterceptors(List customPostCommandInterceptors) { + this.customPostCommandInterceptors = customPostCommandInterceptors; + return this; + } + + public List getCommandInterceptors() { + return commandInterceptors; + } + + public AbstractEngineConfiguration setCommandInterceptors(List commandInterceptors) { + this.commandInterceptors = commandInterceptors; + return this; + } + + public Map getEngineConfigurations() { + return engineConfigurations; + } + + public AbstractEngineConfiguration setEngineConfigurations(Map 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 getServiceConfigurations() { + return serviceConfigurations; + } + + public AbstractEngineConfiguration setServiceConfigurations(Map 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 getEventRegistryEventConsumers() { + return eventRegistryEventConsumers; + } + + public AbstractEngineConfiguration setEventRegistryEventConsumers(Map 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 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> getCustomMybatisMappers() { + return customMybatisMappers; + } + + public AbstractEngineConfiguration setCustomMybatisMappers(Set> customMybatisMappers) { + this.customMybatisMappers = customMybatisMappers; + return this; + } + + public Set getCustomMybatisXMLMappers() { + return customMybatisXMLMappers; + } + + public AbstractEngineConfiguration setCustomMybatisXMLMappers(Set customMybatisXMLMappers) { + this.customMybatisXMLMappers = customMybatisXMLMappers; + return this; + } + + public Set getDependentEngineMyBatisXmlMappers() { + return dependentEngineMyBatisXmlMappers; + } + + public AbstractEngineConfiguration setCustomMybatisInterceptors(List customMybatisInterceptors) { + this.customMybatisInterceptors = customMybatisInterceptors; + return this; + } + + public List getCustomMybatisInterceptors() { + return customMybatisInterceptors; + } + + public AbstractEngineConfiguration setDependentEngineMyBatisXmlMappers(Set dependentEngineMyBatisXmlMappers) { + this.dependentEngineMyBatisXmlMappers = dependentEngineMyBatisXmlMappers; + return this; + } + + public List getDependentEngineMybatisTypeAliasConfigs() { + return dependentEngineMybatisTypeAliasConfigs; + } + + public AbstractEngineConfiguration setDependentEngineMybatisTypeAliasConfigs(List dependentEngineMybatisTypeAliasConfigs) { + this.dependentEngineMybatisTypeAliasConfigs = dependentEngineMybatisTypeAliasConfigs; + return this; + } + + public List getDependentEngineMybatisTypeHandlerConfigs() { + return dependentEngineMybatisTypeHandlerConfigs; + } + + public AbstractEngineConfiguration setDependentEngineMybatisTypeHandlerConfigs(List dependentEngineMybatisTypeHandlerConfigs) { + this.dependentEngineMybatisTypeHandlerConfigs = dependentEngineMybatisTypeHandlerConfigs; + return this; + } + + public List getCustomSessionFactories() { + return customSessionFactories; + } + + public AbstractEngineConfiguration addCustomSessionFactory(SessionFactory sessionFactory) { + if (customSessionFactories == null) { + customSessionFactories = new ArrayList<>(); + } + customSessionFactories.add(sessionFactory); + return this; + } + + public AbstractEngineConfiguration setCustomSessionFactories(List 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; + } + + 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, SessionFactory> getSessionFactories() { + return sessionFactories; + } + + public AbstractEngineConfiguration setSessionFactories(Map, 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 getEventListeners() { + return eventListeners; + } + + public AbstractEngineConfiguration setEventListeners(List eventListeners) { + this.eventListeners = eventListeners; + return this; + } + + public Map> getTypedEventListeners() { + return typedEventListeners; + } + + public AbstractEngineConfiguration setTypedEventListeners(Map> typedEventListeners) { + this.typedEventListeners = typedEventListeners; + return this; + } + + public List getAdditionalEventDispatchActions() { + return additionalEventDispatchActions; + } + + public AbstractEngineConfiguration setAdditionalEventDispatchActions(List 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> 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 getDeployers() { + return deployers; + } + + public AbstractEngineConfiguration setDeployers(List deployers) { + this.deployers = deployers; + return this; + } + + public List getCustomPreDeployers() { + return customPreDeployers; + } + + public AbstractEngineConfiguration setCustomPreDeployers(List customPreDeployers) { + this.customPreDeployers = customPreDeployers; + return this; + } + + public List getCustomPostDeployers() { + return customPostDeployers; + } + + public AbstractEngineConfiguration setCustomPostDeployers(List customPostDeployers) { + this.customPostDeployers = customPostDeployers; + return this; + } + + public boolean isEnableConfiguratorServiceLoader() { + return enableConfiguratorServiceLoader; + } + + public AbstractEngineConfiguration setEnableConfiguratorServiceLoader(boolean enableConfiguratorServiceLoader) { + this.enableConfiguratorServiceLoader = enableConfiguratorServiceLoader; + return this; + } + + public List 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 getAllConfigurators() { + return allConfigurators; + } + + public AbstractEngineConfiguration setConfigurators(List 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; + } +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/db/DbSqlSessionFactory.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/db/DbSqlSessionFactory.java new file mode 100644 index 0000000..cbea955 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/db/DbSqlSessionFactory.java @@ -0,0 +1,394 @@ +/* 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.db; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.impl.context.Context; +import org.flowable.common.engine.impl.interceptor.CommandContext; +import org.flowable.common.engine.impl.interceptor.Session; +import org.flowable.common.engine.impl.interceptor.SessionFactory; +import org.flowable.common.engine.impl.persistence.cache.EntityCache; +import org.flowable.common.engine.impl.persistence.entity.Entity; + +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Tom Baeyens + * @author Joram Barrez + */ +public class DbSqlSessionFactory implements SessionFactory { + + protected Map> databaseSpecificStatements = new HashMap<>(); + + protected String databaseType; + protected String databaseTablePrefix = ""; + protected boolean tablePrefixIsSchema; + + protected String databaseCatalog; + protected String databaseSchema; + protected SqlSessionFactory sqlSessionFactory; + protected Map statementMappings; + + protected Map, String> insertStatements = new ConcurrentHashMap<>(); + protected Map, String> updateStatements = new ConcurrentHashMap<>(); + protected Map, String> deleteStatements = new ConcurrentHashMap<>(); + protected Map, String> selectStatements = new ConcurrentHashMap<>(); + + protected List> insertionOrder = new ArrayList<>(); + protected List> deletionOrder = new ArrayList<>(); + + protected boolean isDbHistoryUsed = true; + + protected Set> bulkInserteableEntityClasses = new HashSet<>(); + protected Map, String> bulkInsertStatements = new ConcurrentHashMap<>(); + + protected int maxNrOfStatementsInBulkInsert = 100; + + protected Map> logicalNameToClassMapping = new ConcurrentHashMap<>(); + + protected boolean usePrefixId; + + public DbSqlSessionFactory(boolean usePrefixId) { + this.usePrefixId = usePrefixId; + } + + @Override + public Class getSessionType() { + return DbSqlSession.class; + } + + @Override + public Session openSession(CommandContext commandContext) { + DbSqlSession dbSqlSession = createDbSqlSession(); + // 当前系统适配 dm,如果存在 schema 为空的情况,从 connection 获取 + try { + if (getDatabaseSchema() == null || getDatabaseSchema().length() == 0){ + String schemaFromUrl = extractSchemaFromJdbcUrl(dbSqlSession.getSqlSession().getConnection()); + if (schemaFromUrl != null && schemaFromUrl.length() > 0) { + setDatabaseSchema(schemaFromUrl); + } else { + setDatabaseSchema(dbSqlSession.getSqlSession().getConnection().getSchema()); + } + } + dbSqlSession.getSqlSession().getConnection().getSchema(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + + if (getDatabaseSchema() != null && getDatabaseSchema().length() > 0) { + try { + dbSqlSession.getSqlSession().getConnection().setSchema(getDatabaseSchema()); + } catch (SQLException e) { + throw new FlowableException("Could not set database schema on connection", e); + } + } + if (getDatabaseCatalog() != null && getDatabaseCatalog().length() > 0) { + try { + dbSqlSession.getSqlSession().getConnection().setCatalog(getDatabaseCatalog()); + } catch (SQLException e) { + throw new FlowableException("Could not set database catalog on connection", e); + } + } + if (dbSqlSession.getSqlSession().getConnection() == null) { + throw new FlowableException("Invalid dbSqlSession: no active connection found"); + } + return dbSqlSession; + } + + protected DbSqlSession createDbSqlSession() { + return new DbSqlSession(this, Context.getCommandContext().getSession(EntityCache.class)); + } + + // insert, update and delete statements + // ///////////////////////////////////// + + public String getInsertStatement(Entity object) { + return getStatement(object.getClass(), insertStatements, "insert"); + } + + public String getInsertStatement(Class clazz) { + return getStatement(clazz, insertStatements, "insert"); + } + + public String getUpdateStatement(Entity object) { + return getStatement(object.getClass(), updateStatements, "update"); + } + + public String getDeleteStatement(Class entityClass) { + return getStatement(entityClass, deleteStatements, "delete"); + } + + public String getSelectStatement(Class entityClass) { + return getStatement(entityClass, selectStatements, "select"); + } + + protected String getStatement(Class entityClass, Map, String> cachedStatements, String prefix) { + String statement = cachedStatements.get(entityClass); + if (statement != null) { + return statement; + } + statement = prefix + entityClass.getSimpleName(); + if (statement.endsWith("Impl")) { + statement = statement.substring(0, statement.length() - 10); // removing 'entityImpl' + } else { + statement = statement.substring(0, statement.length() - 6); // removing 'entity' + } + cachedStatements.put(entityClass, statement); + return statement; + } + + // db specific mappings + // ///////////////////////////////////////////////////// + + protected void addDatabaseSpecificStatement(String databaseType, String activitiStatement, String ibatisStatement) { + Map specificStatements = databaseSpecificStatements.get(databaseType); + if (specificStatements == null) { + specificStatements = new HashMap<>(); + databaseSpecificStatements.put(databaseType, specificStatements); + } + specificStatements.put(activitiStatement, ibatisStatement); + } + + public String mapStatement(String statement) { + if (statementMappings == null) { + return statement; + } + String mappedStatement = statementMappings.get(statement); + return (mappedStatement != null ? mappedStatement : statement); + } + + // customized getters and setters + // /////////////////////////////////////////// + + public void setDatabaseType(String databaseType) { + this.databaseType = databaseType; + this.statementMappings = databaseSpecificStatements.get(databaseType); + } + + public boolean isMysql() { + return "mysql".equals(getDatabaseType()); + } + + public boolean isOracle() { + return "oracle".equals(getDatabaseType()); + } + + public Boolean isBulkInsertable(Class entityClass) { + return bulkInserteableEntityClasses != null && bulkInserteableEntityClasses.contains(entityClass); + } + + @SuppressWarnings("rawtypes") + public String getBulkInsertStatement(Class clazz) { + return getStatement(clazz, bulkInsertStatements, "bulkInsert"); + } + + public Set> getBulkInserteableEntityClasses() { + return bulkInserteableEntityClasses; + } + + public void setBulkInserteableEntityClasses(Set> bulkInserteableEntityClasses) { + this.bulkInserteableEntityClasses = bulkInserteableEntityClasses; + } + + public int getMaxNrOfStatementsInBulkInsert() { + return maxNrOfStatementsInBulkInsert; + } + + public void setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) { + this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert; + } + + public Map, String> getBulkInsertStatements() { + return bulkInsertStatements; + } + + public void setBulkInsertStatements(Map, String> bulkInsertStatements) { + this.bulkInsertStatements = bulkInsertStatements; + } + + // getters and setters ////////////////////////////////////////////////////// + + public SqlSessionFactory getSqlSessionFactory() { + return sqlSessionFactory; + } + + public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { + this.sqlSessionFactory = sqlSessionFactory; + } + + public String getDatabaseType() { + return databaseType; + } + + public Map> getDatabaseSpecificStatements() { + return databaseSpecificStatements; + } + + public void setDatabaseSpecificStatements(Map> databaseSpecificStatements) { + this.databaseSpecificStatements = databaseSpecificStatements; + } + + public Map getStatementMappings() { + return statementMappings; + } + + public void setStatementMappings(Map statementMappings) { + this.statementMappings = statementMappings; + } + + public Map, String> getInsertStatements() { + return insertStatements; + } + + public void setInsertStatements(Map, String> insertStatements) { + this.insertStatements = insertStatements; + } + + public Map, String> getUpdateStatements() { + return updateStatements; + } + + public void setUpdateStatements(Map, String> updateStatements) { + this.updateStatements = updateStatements; + } + + public Map, String> getDeleteStatements() { + return deleteStatements; + } + + public void setDeleteStatements(Map, String> deleteStatements) { + this.deleteStatements = deleteStatements; + } + + public Map, String> getSelectStatements() { + return selectStatements; + } + + public void setSelectStatements(Map, String> selectStatements) { + this.selectStatements = selectStatements; + } + + public boolean isDbHistoryUsed() { + return isDbHistoryUsed; + } + + public void setDbHistoryUsed(boolean isDbHistoryUsed) { + this.isDbHistoryUsed = isDbHistoryUsed; + } + + public void setDatabaseTablePrefix(String databaseTablePrefix) { + this.databaseTablePrefix = databaseTablePrefix; + } + + public String getDatabaseTablePrefix() { + return databaseTablePrefix; + } + + public String getDatabaseCatalog() { + return databaseCatalog; + } + + public void setDatabaseCatalog(String databaseCatalog) { + this.databaseCatalog = databaseCatalog; + } + + public String getDatabaseSchema() { + return databaseSchema; + } + + public void setDatabaseSchema(String databaseSchema) { + this.databaseSchema = databaseSchema; + } + + public void setTablePrefixIsSchema(boolean tablePrefixIsSchema) { + this.tablePrefixIsSchema = tablePrefixIsSchema; + } + + public boolean isTablePrefixIsSchema() { + return tablePrefixIsSchema; + } + + public List> getInsertionOrder() { + return insertionOrder; + } + + public void setInsertionOrder(List> insertionOrder) { + this.insertionOrder = insertionOrder; + } + + public List> getDeletionOrder() { + return deletionOrder; + } + + public void setDeletionOrder(List> deletionOrder) { + this.deletionOrder = deletionOrder; + } + public void addLogicalEntityClassMapping(String logicalName, Class entityClass) { + logicalNameToClassMapping.put(logicalName, entityClass); + } + + public Map> getLogicalNameToClassMapping() { + return logicalNameToClassMapping; + } + + public void setLogicalNameToClassMapping(Map> logicalNameToClassMapping) { + this.logicalNameToClassMapping = logicalNameToClassMapping; + } + + public boolean isUsePrefixId() { + return usePrefixId; + } + + public void setUsePrefixId(boolean usePrefixId) { + this.usePrefixId = usePrefixId; + } + + private String extractSchemaFromJdbcUrl(java.sql.Connection connection) { + if (connection == null) { + return null; + } + try { + String url = connection.getMetaData().getURL(); + if (url == null || url.isEmpty()) { + return null; + } + int queryIndex = url.indexOf('?'); + if (queryIndex < 0 || queryIndex == url.length() - 1) { + return null; + } + String query = url.substring(queryIndex + 1); + String[] parts = query.split("[&;]"); + for (String part : parts) { + int eqIndex = part.indexOf('='); + if (eqIndex <= 0 || eqIndex == part.length() - 1) { + continue; + } + String key = part.substring(0, eqIndex).trim().toLowerCase(Locale.ROOT); + if ("schema".equals(key) || "currentschema".equals(key) || "current_schema".equals(key)) { + String value = part.substring(eqIndex + 1).trim(); + if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) { + value = value.substring(1, value.length() - 1); + } + return value; + } + } + } catch (SQLException ignored) { + return null; + } + return null; + } +}