From 455aaea10a9ab95b84a165532af9ed0797f399b9 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 1 Jul 2025 07:30:25 +0000 Subject: [PATCH] =?UTF-8?q?v=201.0=201.=20=E6=96=B0=E5=A2=9E=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=B8=8E=E9=83=A8=E9=97=A8=EF=BC=8C=E4=B8=80=E5=AF=B9?= =?UTF-8?q?=E5=A4=9A=E7=9A=84=E5=85=B3=E7=B3=BB=EF=BC=9B=202.=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=AE=A1=E7=90=86=E5=A4=9A=E9=83=A8=E9=97=A8=E7=94=A8?= =?UTF-8?q?=E6=88=B7=EF=BC=8C=E5=A6=82=E6=9E=9C=E6=9C=89=E4=B8=BA=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E7=9A=84=E5=A4=9A=E4=B8=AA=E9=83=A8=E9=97=A8=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E8=BF=9B=E8=A1=8C=E9=80=89=E6=8B=A9=E7=99=BB=E5=BD=95?= =?UTF-8?q?=EF=BC=88=E9=80=89=E6=8B=A9=E5=90=8E=EF=BC=8C=E7=9B=B4=E5=88=B0?= =?UTF-8?q?=E4=B8=8B=E6=AC=A1=E5=8F=98=E6=9B=B4=E8=AE=BF=E9=97=AE=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E5=89=8D=EF=BC=8C=E5=8F=AA=E8=83=BD=E8=AE=BF=E9=97=AE?= =?UTF-8?q?=E6=AD=A4=E6=AC=A1=E9=80=89=E6=8B=A9=E5=85=AC=E7=9A=84=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E6=95=B0=E6=8D=AE=EF=BC=8C=E4=BD=BF=E7=94=A8=20compan?= =?UTF-8?q?y=5Fid=20=E6=8E=A7=E5=88=B6=EF=BC=8C=E5=90=8E=E7=BB=AD=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E6=AD=A4=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90=E7=9A=84?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=EF=BC=89=EF=BC=9B=203.=20sql=20=E8=BD=AC?= =?UTF-8?q?=E5=8C=96=E5=B7=A5=E5=85=B7=E4=BF=AE=E5=A4=8D=EF=BC=8C=E7=8E=B0?= =?UTF-8?q?=E5=9C=A8=E5=8F=AF=E4=BB=A5=E6=AD=A3=E7=A1=AE=E7=9A=84=E5=AF=B9?= =?UTF-8?q?=20mysql=20=E8=BF=9B=E8=A1=8C=E4=B8=8D=E5=90=8C=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=AE=9E=E4=BE=8B=E7=9A=84=E8=BD=AC=E5=8C=96?= =?UTF-8?q?=E4=BA=86=EF=BC=9B=204.=20=E6=89=80=E6=9C=89=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E4=B8=BB=E9=94=AE=EF=BC=8C=E4=BF=AE=E6=94=B9=E4=B8=BA=E5=88=86?= =?UTF-8?q?=E5=B8=83=E5=BC=8F=20Id=20=E5=AE=9E=E7=8E=B0=EF=BC=9B=205.=20?= =?UTF-8?q?=E8=A1=A5=E5=85=A8=E5=9C=A8=E5=88=9D=E5=A7=8B=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E4=B8=AD=E6=B2=A1=E6=9C=89=E8=A2=AB=E7=BA=B3=E5=85=A5=E7=9A=84?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E9=A2=84=E5=88=B6=E5=8A=9F=E8=83=BD=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 06b278563eddf5897bf896b3a67ada079b821ea0) --- LICENSE | 20 + .../impl/AbstractEngineConfiguration.java | 2068 ++++++++++++++++ sql/mysql/bpm/bpm.sql | 145 ++ sql/mysql/bpm/bpm_tables.sql | 171 ++ .../druid/pool/DruidPooledStatement.java | 781 ++++++ .../liquibase/database/core/DmDatabase.java | 546 +++++ .../liquibase/datatype/core/BooleanType.java | 149 ++ .../impl/AbstractEngineConfiguration.java | 2094 +++++++++++++++++ .../resources/images/jigsaw/original/bg4.png | Bin 0 -> 27859 bytes .../resources/images/jigsaw/original/bg5.png | Bin 0 -> 24036 bytes .../resources/images/jigsaw/original/bg6.png | Bin 0 -> 19160 bytes .../resources/images/jigsaw/original/bg7.png | Bin 0 -> 21445 bytes .../resources/images/jigsaw/original/bg8.png | Bin 0 -> 30332 bytes .../resources/images/jigsaw/original/bg9.png | Bin 0 -> 26977 bytes 14 files changed, 5974 insertions(+) create mode 100644 LICENSE create mode 100644 sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java create mode 100644 sql/mysql/bpm/bpm.sql create mode 100644 sql/mysql/bpm/bpm_tables.sql create mode 100644 yudao-module-bpm/yudao-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java create mode 100644 yudao-module-bpm/yudao-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java create mode 100644 yudao-module-bpm/yudao-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java create mode 100644 yudao-module-bpm/yudao-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg4.png create mode 100644 yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg5.png create mode 100644 yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg6.png create mode 100644 yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg7.png create mode 100644 yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg8.png create mode 100644 yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg9.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..af732e37 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2021 yudao-cloud + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java b/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java new file mode 100644 index 00000000..33c52d55 --- /dev/null +++ b/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java @@ -0,0 +1,2068 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.common.engine.impl; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.Set; + +import javax.naming.InitialContext; +import javax.sql.DataSource; + +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.builder.xml.XMLConfigBuilder; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.datasource.pooled.PooledDataSource; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.apache.ibatis.transaction.managed.ManagedTransactionFactory; +import org.apache.ibatis.type.ArrayTypeHandler; +import org.apache.ibatis.type.BigDecimalTypeHandler; +import org.apache.ibatis.type.BlobInputStreamTypeHandler; +import org.apache.ibatis.type.BlobTypeHandler; +import org.apache.ibatis.type.BooleanTypeHandler; +import org.apache.ibatis.type.ByteTypeHandler; +import org.apache.ibatis.type.ClobTypeHandler; +import org.apache.ibatis.type.DateOnlyTypeHandler; +import org.apache.ibatis.type.DateTypeHandler; +import org.apache.ibatis.type.DoubleTypeHandler; +import org.apache.ibatis.type.FloatTypeHandler; +import org.apache.ibatis.type.IntegerTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.LongTypeHandler; +import org.apache.ibatis.type.NClobTypeHandler; +import org.apache.ibatis.type.NStringTypeHandler; +import org.apache.ibatis.type.ShortTypeHandler; +import org.apache.ibatis.type.SqlxmlTypeHandler; +import org.apache.ibatis.type.StringTypeHandler; +import org.apache.ibatis.type.TimeOnlyTypeHandler; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.api.delegate.event.FlowableEventListener; +import org.flowable.common.engine.api.engine.EngineLifecycleListener; +import org.flowable.common.engine.impl.agenda.AgendaOperationRunner; +import org.flowable.common.engine.impl.cfg.CommandExecutorImpl; +import org.flowable.common.engine.impl.cfg.IdGenerator; +import org.flowable.common.engine.impl.cfg.TransactionContextFactory; +import org.flowable.common.engine.impl.cfg.standalone.StandaloneMybatisTransactionContextFactory; +import org.flowable.common.engine.impl.db.CommonDbSchemaManager; +import org.flowable.common.engine.impl.db.DbSqlSessionFactory; +import org.flowable.common.engine.impl.db.LogSqlExecutionTimePlugin; +import org.flowable.common.engine.impl.db.MybatisTypeAliasConfigurator; +import org.flowable.common.engine.impl.db.MybatisTypeHandlerConfigurator; +import org.flowable.common.engine.impl.db.SchemaManager; +import org.flowable.common.engine.impl.event.EventDispatchAction; +import org.flowable.common.engine.impl.event.FlowableEventDispatcherImpl; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandConfig; +import org.flowable.common.engine.impl.interceptor.CommandContextFactory; +import org.flowable.common.engine.impl.interceptor.CommandContextInterceptor; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.common.engine.impl.interceptor.CommandInterceptor; +import org.flowable.common.engine.impl.interceptor.CrDbRetryInterceptor; +import org.flowable.common.engine.impl.interceptor.DefaultCommandInvoker; +import org.flowable.common.engine.impl.interceptor.LogInterceptor; +import org.flowable.common.engine.impl.interceptor.SessionFactory; +import org.flowable.common.engine.impl.interceptor.TransactionContextInterceptor; +import org.flowable.common.engine.impl.lock.LockManager; +import org.flowable.common.engine.impl.lock.LockManagerImpl; +import org.flowable.common.engine.impl.logging.LoggingListener; +import org.flowable.common.engine.impl.logging.LoggingSession; +import org.flowable.common.engine.impl.logging.LoggingSessionFactory; +import org.flowable.common.engine.impl.persistence.GenericManagerFactory; +import org.flowable.common.engine.impl.persistence.StrongUuidGenerator; +import org.flowable.common.engine.impl.persistence.cache.EntityCache; +import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl; +import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManager; +import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.Entity; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManager; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.TableDataManager; +import org.flowable.common.engine.impl.persistence.entity.TableDataManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.data.ByteArrayDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.PropertyDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisByteArrayDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisPropertyDataManager; +import org.flowable.common.engine.impl.runtime.Clock; +import org.flowable.common.engine.impl.service.CommonEngineServiceImpl; +import org.flowable.common.engine.impl.util.DefaultClockImpl; +import org.flowable.common.engine.impl.util.IoUtil; +import org.flowable.common.engine.impl.util.ReflectUtil; +import org.flowable.eventregistry.api.EventRegistryEventConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +public abstract class AbstractEngineConfiguration { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** The tenant id indicating 'no tenant' */ + public static final String NO_TENANT_ID = ""; + + /** + * Checks the version of the DB schema against the library when the form engine is being created and throws an exception if the versions don't match. + */ + public static final String DB_SCHEMA_UPDATE_FALSE = "false"; + public static final String DB_SCHEMA_UPDATE_CREATE = "create"; + public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop"; + + /** + * Creates the schema when the form engine is being created and drops the schema when the form engine is being closed. + */ + public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create"; + + /** + * Upon building of the process engine, a check is performed and an update of the schema is performed if it is necessary. + */ + public static final String DB_SCHEMA_UPDATE_TRUE = "true"; + + protected boolean forceCloseMybatisConnectionPool = true; + + protected String databaseType; + protected String jdbcDriver = "org.h2.Driver"; + protected String jdbcUrl = "jdbc:h2:tcp://localhost/~/flowable"; + protected String jdbcUsername = "sa"; + protected String jdbcPassword = ""; + protected String dataSourceJndiName; + protected int jdbcMaxActiveConnections = 16; + protected int jdbcMaxIdleConnections = 8; + protected int jdbcMaxCheckoutTime; + protected int jdbcMaxWaitTime; + protected boolean jdbcPingEnabled; + protected String jdbcPingQuery; + protected int jdbcPingConnectionNotUsedFor; + protected int jdbcDefaultTransactionIsolationLevel; + protected DataSource dataSource; + protected SchemaManager commonSchemaManager; + protected SchemaManager schemaManager; + protected Command 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 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); // dhb52: DM support + 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 List getCustomPreCommandInterceptors() { + return customPreCommandInterceptors; + } + + public AbstractEngineConfiguration setCustomPreCommandInterceptors(List customPreCommandInterceptors) { + this.customPreCommandInterceptors = customPreCommandInterceptors; + return this; + } + + public List getCustomPostCommandInterceptors() { + return customPostCommandInterceptors; + } + + 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; + } + + /** + * @return name of the default tenant + * @deprecated use {@link AbstractEngineConfiguration#getDefaultTenantProvider()} instead + */ + @Deprecated + public String getDefaultTenantValue() { + return getDefaultTenantProvider().getDefaultTenant(null, null, null); + } + + public AbstractEngineConfiguration setDefaultTenantValue(String defaultTenantValue) { + this.defaultTenantProvider = (tenantId, scope, scopeKey) -> defaultTenantValue; + return this; + } + + public DefaultTenantProvider getDefaultTenantProvider() { + return defaultTenantProvider; + } + + public AbstractEngineConfiguration setDefaultTenantProvider(DefaultTenantProvider defaultTenantProvider) { + this.defaultTenantProvider = defaultTenantProvider; + return this; + } + + public boolean isEnableLogSqlExecutionTime() { + return enableLogSqlExecutionTime; + } + + public void setEnableLogSqlExecutionTime(boolean enableLogSqlExecutionTime) { + this.enableLogSqlExecutionTime = enableLogSqlExecutionTime; + } + + public Map, 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/sql/mysql/bpm/bpm.sql b/sql/mysql/bpm/bpm.sql new file mode 100644 index 00000000..1c97ee39 --- /dev/null +++ b/sql/mysql/bpm/bpm.sql @@ -0,0 +1,145 @@ +DROP TABLE IF EXISTS `bpm_category`; +CREATE TABLE `bpm_category` ( + `id` bigint(0) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '分类编号', + `name` varchar(255) DEFAULT NULL COMMENT '分类名', + `code` varchar(255) DEFAULT NULL COMMENT '分类标志', + `description` varchar(255) DEFAULT NULL COMMENT '分类描述', + `status` int(11) DEFAULT NULL COMMENT '分类状态,枚举 CommonStatusEnum', + `sort` int(11) DEFAULT NULL COMMENT '分类排序', + `create_time` datetime COMMENT '创建时间', + `update_time` datetime COMMENT '最后更新时间', + `creator` varchar(255) COMMENT '创建者,目前使用 SysUser 的 id 编号', + `updater` varchar(255) COMMENT '更新者,目前使用 SysUser 的 id 编号', + `deleted` tinyint(1) DEFAULT NULL COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号' +) ENGINE=InnoDB COMMENT='流程分类'; + +DROP TABLE IF EXISTS `bpm_form`; +CREATE TABLE `bpm_form` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表单名', + `status` tinyint NOT NULL COMMENT '开启状态', + `conf` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表单的配置', + `fields` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表单项的数组', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '工作流的表单定义'; + + +DROP TABLE IF EXISTS `bpm_oa_leave`; +CREATE TABLE `bpm_oa_leave` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '请假表单主键', + `user_id` bigint NOT NULL COMMENT '申请人的用户编号', + `type` tinyint NOT NULL COMMENT '请假类型', + `reason` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '请假原因', + `start_time` datetime NOT NULL COMMENT '开始时间', + `end_time` datetime NOT NULL COMMENT '结束时间', + `day` tinyint NOT NULL COMMENT '请假天数', + `result` tinyint NOT NULL COMMENT '请假结果', + `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '流程实例的编号', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OA 请假申请表'; + + +DROP TABLE IF EXISTS `bpm_process_definition_info`; +CREATE TABLE `bpm_process_definition_info` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `process_definition_id` varchar(64) NOT NULL COMMENT '流程定义的编号', + `model_id` varchar(64) NOT NULL COMMENT '流程模型的编号', + `icon` varchar(255) DEFAULT NULL COMMENT '图标', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `form_type` tinyint NOT NULL COMMENT '表单类型', + `form_id` bigint NULL DEFAULT NULL COMMENT '表单编号', + `form_conf` varchar(1000) DEFAULT NULL COMMENT '表单的配置', + `form_fields` varchar(5000) DEFAULT NULL COMMENT '表单项的数组', + `form_custom_create_path` varchar(255) DEFAULT NULL COMMENT '自定义表单的提交路径', + `form_custom_view_path` varchar(255) DEFAULT NULL COMMENT '自定义表单的查看路径', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Bpm 流程定义信息表'; + + +DROP TABLE IF EXISTS `bpm_process_expression`; +CREATE TABLE `bpm_process_expression` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(64) DEFAULT NULL COMMENT '流程实例的名字', + `status` tinyint NOT NULL COMMENT '流程实例的状态', + `expression` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表达式', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程表达式'; + +DROP TABLE IF EXISTS `bpm_process_instance_copy`; +CREATE TABLE `bpm_process_instance_copy` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `start_user_id` bigint NOT NULL COMMENT '发起流程的用户编号', + `process_instance_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '流程实例的名字', + `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '流程实例的编号', + `category` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '流程分类', + `task_id` varchar(64) DEFAULT NULL COMMENT '任务主键', + `task_name` varchar(64) DEFAULT NULL COMMENT '任务名称', + `user_id` bigint NOT NULL COMMENT '用户编号(被抄送的用户编号)', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 296 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程抄送表'; + +DROP TABLE IF EXISTS `bpm_process_listener`; +CREATE TABLE `bpm_process_listener` ( + `id` bigint(20) NOT NULL COMMENT '主键 ID,自增', + `name` varchar(255) DEFAULT NULL COMMENT '监听器名字', + `status` int(11) DEFAULT NULL COMMENT '状态', + `type` varchar(255) DEFAULT NULL COMMENT '监听类型', + `event` varchar(255) DEFAULT NULL COMMENT '监听事件', + `value_type` varchar(255) DEFAULT NULL COMMENT '值类型', + `value` varchar(255) DEFAULT NULL COMMENT '值', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程监听器'; + +DROP TABLE IF EXISTS `bpm_user_group`; +CREATE TABLE `bpm_user_group` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(30) DEFAULT '' COMMENT '组名', + `description` varchar(255) DEFAULT '' COMMENT '描述', + `status` tinyint NOT NULL COMMENT '状态(0正常 1停用)', + `user_ids` varchar(1024) DEFAULT '0' COMMENT '成员编号数组', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 113 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户组'; diff --git a/sql/mysql/bpm/bpm_tables.sql b/sql/mysql/bpm/bpm_tables.sql new file mode 100644 index 00000000..7bab8041 --- /dev/null +++ b/sql/mysql/bpm/bpm_tables.sql @@ -0,0 +1,171 @@ +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_category`; +CREATE TABLE `bpm_category` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号', + `name` varchar(255) NOT NULL COMMENT '分类名', + `code` varchar(255) NOT NULL COMMENT '分类标志', + `description` varchar(255) DEFAULT NULL COMMENT '分类描述', + `status` int NOT NULL COMMENT '分类状态', + `sort` int DEFAULT NULL COMMENT '分类排序', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BPM 流程分类'; + +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_form`; +CREATE TABLE `bpm_form` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(255) NOT NULL COMMENT '表单名', + `status` int NOT NULL COMMENT '状态', + `conf` varchar(2000) DEFAULT NULL COMMENT '表单的配置', + `fields` varchar(2000) DEFAULT NULL COMMENT '表单项的数组', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BPM 工作流的表单定义'; + +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_process_definition_info`; +CREATE TABLE `bpm_process_definition_info` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `process_definition_id` varchar(255) NOT NULL COMMENT '流程定义的编号', + `model_id` varchar(255) DEFAULT NULL COMMENT '流程模型的编号', + `model_type` int DEFAULT NULL COMMENT '流程模型的类型', + `category` varchar(255) DEFAULT NULL COMMENT '流程分类的编码', + `icon` varchar(255) DEFAULT NULL COMMENT '图标', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `form_type` int DEFAULT NULL COMMENT '表单类型', + `form_id` bigint DEFAULT NULL COMMENT '动态表单编号', + `form_conf` varchar(2000) DEFAULT NULL COMMENT '表单的配置', + `form_fields` varchar(2000) DEFAULT NULL COMMENT '表单项的数组', + `form_custom_create_path` varchar(255) DEFAULT NULL COMMENT '自定义表单的提交路径', + `form_custom_view_path` varchar(255) DEFAULT NULL COMMENT '自定义表单的查看路径', + `simple_model` varchar(2000) DEFAULT NULL COMMENT 'SIMPLE 设计器模型数据 json 格式', + `visible` bit(1) DEFAULT NULL COMMENT '是否可见', + `sort` bigint DEFAULT NULL COMMENT '排序值', + `start_user_ids` varchar(2000) DEFAULT NULL COMMENT '可发起用户编号数组', + `start_dept_ids` varchar(2000) DEFAULT NULL COMMENT '可发起部门编号数组', + `manager_user_ids` varchar(2000) DEFAULT NULL COMMENT '可管理用户编号数组', + `allow_cancel_running_process` bit(1) DEFAULT NULL COMMENT '是否允许撤销审批中的申请', + `process_id_rule` varchar(2000) DEFAULT NULL COMMENT '流程 ID 规则', + `auto_approval_type` int DEFAULT NULL COMMENT '自动去重类型', + `title_setting` varchar(2000) DEFAULT NULL COMMENT '标题设置', + `summary_setting` varchar(2000) DEFAULT NULL COMMENT '摘要设置', + `process_before_trigger_setting` varchar(2000) DEFAULT NULL COMMENT '流程前置通知设置', + `process_after_trigger_setting` varchar(2000) DEFAULT NULL COMMENT '流程后置通知设置', + `task_before_trigger_setting` varchar(2000) DEFAULT NULL COMMENT '任务前置通知设置', + `task_after_trigger_setting` varchar(2000) DEFAULT NULL COMMENT '任务后置通知设置', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BPM 流程定义的拓信息'; + +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_process_expression`; +CREATE TABLE `bpm_process_expression` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(255) NOT NULL COMMENT '表达式名字', + `status` int NOT NULL COMMENT '表达式状态', + `expression` varchar(2000) NOT NULL COMMENT '表达式', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BPM 流程表达式'; + +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_process_listener`; +CREATE TABLE `bpm_process_listener` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键 ID', + `name` varchar(255) NOT NULL COMMENT '监听器名字', + `status` int NOT NULL COMMENT '状态', + `type` varchar(64) NOT NULL COMMENT '监听类型', + `event` varchar(64) DEFAULT NULL COMMENT '监听事件', + `value_type` varchar(64) DEFAULT NULL COMMENT '值类型', + `value` varchar(255) DEFAULT NULL COMMENT '值', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BPM 流程监听器'; + +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_user_group`; +CREATE TABLE `bpm_user_group` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(255) NOT NULL COMMENT '组名', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + `status` int NOT NULL COMMENT '状态', + `user_ids` varchar(2000) DEFAULT NULL COMMENT '成员用户编号数组', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BPM 用户组'; + +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_oa_leave`; +CREATE TABLE `bpm_oa_leave` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '请假表单主键', + `user_id` bigint NOT NULL COMMENT '申请人的用户编号', + `type` varchar(255) NOT NULL COMMENT '请假类型', + `reason` varchar(255) DEFAULT NULL COMMENT '原因', + `start_time` datetime DEFAULT NULL COMMENT '开始时间', + `end_time` datetime DEFAULT NULL COMMENT '结束时间', + `day` bigint DEFAULT NULL COMMENT '请假天数', + `status` int DEFAULT NULL COMMENT '审批结果', + `process_instance_id` varchar(255) DEFAULT NULL COMMENT '对应的流程编号', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OA 请假申请'; + +-- 删除表,如果已存在 +DROP TABLE IF EXISTS `bpm_process_instance_copy`; +CREATE TABLE `bpm_process_instance_copy` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `start_user_id` bigint DEFAULT NULL COMMENT '发起人 Id', + `process_instance_name` varchar(255) DEFAULT NULL COMMENT '流程名', + `process_instance_id` varchar(255) DEFAULT NULL COMMENT '流程实例的编号', + `process_definition_id` varchar(255) DEFAULT NULL COMMENT '流程实例的流程定义编号', + `category` varchar(255) DEFAULT NULL COMMENT '流程分类', + `activity_id` varchar(255) DEFAULT NULL COMMENT '流程活动的编号', + `activity_name` varchar(255) DEFAULT NULL COMMENT '流程活动的名字', + `task_id` varchar(255) DEFAULT NULL COMMENT '流程活动的编号', + `user_id` bigint DEFAULT NULL COMMENT '用户编号(被抄送的用户编号)', + `reason` varchar(255) DEFAULT NULL COMMENT '抄送意见', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` bit(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程抄送'; diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java new file mode 100644 index 00000000..1c86d9e7 --- /dev/null +++ b/yudao-module-bpm/yudao-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/yudao-module-bpm/yudao-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java new file mode 100644 index 00000000..f7558a11 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/liquibase/database/core/DmDatabase.java @@ -0,0 +1,546 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package liquibase.database.core; + +import java.lang.reflect.Method; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import liquibase.CatalogAndSchema; +import liquibase.GlobalConfiguration; +import liquibase.Scope; +import liquibase.database.AbstractJdbcDatabase; +import liquibase.database.DatabaseConnection; +import liquibase.database.OfflineConnection; +import liquibase.database.jvm.JdbcConnection; +import liquibase.exception.DatabaseException; +import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.exception.ValidationErrors; +import liquibase.executor.ExecutorService; +import liquibase.statement.DatabaseFunction; +import liquibase.statement.SequenceCurrentValueFunction; +import liquibase.statement.SequenceNextValueFunction; +import liquibase.statement.UniqueConstraint; +import liquibase.statement.core.RawCallStatement; +import liquibase.statement.core.RawParameterizedSqlStatement; +import liquibase.structure.DatabaseObject; +import liquibase.structure.core.Catalog; +import liquibase.structure.core.Column; +import liquibase.structure.core.Index; +import liquibase.structure.core.PrimaryKey; +import liquibase.structure.core.Schema; +import liquibase.util.JdbcUtil; +import liquibase.util.StringUtil; +import org.apache.commons.lang3.StringUtils; + +public class DmDatabase extends AbstractJdbcDatabase { + private static final String PROXY_USER_REGEX = ".*(?:thin|oci)\\:(.+)/@.*"; + public static final Pattern PROXY_USER_PATTERN = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*"); + private static final String VERSION_REGEX = "(\\d+)\\.(\\d+)\\..*"; + private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\..*"); + public static final String PRODUCT_NAME = "DM DBMS"; + private static final ResourceBundle coreBundle = ResourceBundle.getBundle("liquibase/i18n/liquibase-core"); + protected final int SHORT_IDENTIFIERS_LENGTH = 30; + protected final int LONG_IDENTIFIERS_LEGNTH = 128; + public static final int ORACLE_12C_MAJOR_VERSION = 12; + public static final int ORACLE_23C_MAJOR_VERSION = 23; + private final Set reservedWords = new HashSet(); + private Set userDefinedTypes; + private Map savedSessionNlsSettings; + private Boolean canAccessDbaRecycleBin; + private Integer databaseMajorVersion; + private Integer databaseMinorVersion; + + public DmDatabase() { + super.unquotedObjectsAreUppercased = true; + super.setCurrentDateTimeFunction("SYSTIMESTAMP"); + this.dateFunctions.add(new DatabaseFunction("SYSDATE")); + this.dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP")); + this.dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP")); + super.sequenceNextValueFunction = "%s.nextval"; + super.sequenceCurrentValueFunction = "%s.currval"; + } + + public int getPriority() { + return 1; + } + + private void tryProxySession(String url, Connection con) { + Matcher m = PROXY_USER_PATTERN.matcher(url); + if (m.matches()) { + Properties props = new Properties(); + props.put("PROXY_USER_NAME", m.group(1)); + + try { + Method method = con.getClass().getMethod("openProxySession", Integer.TYPE, Properties.class); + method.setAccessible(true); + method.invoke(con, 1, props); + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage()); + return; + } + + try { + Method method = con.getClass().getMethod("isProxySession"); + method.setAccessible(true); + boolean b = (Boolean)method.invoke(con); + if (!b) { + Scope.getCurrentScope().getLog(this.getClass()).info("Proxy session not established on OracleDatabase: "); + } + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage()); + } + } + + } + + public void setConnection(DatabaseConnection conn) { + this.reservedWords.addAll(Arrays.asList("GROUP", "USER", "SESSION", "PASSWORD", "RESOURCE", "START", "SIZE", "UID", "DESC", "ORDER")); + Connection sqlConn = null; + if (!(conn instanceof OfflineConnection)) { + try { + if (conn instanceof JdbcConnection) { + sqlConn = ((JdbcConnection)conn).getWrappedConnection(); + } + } catch (Exception e) { + throw new UnexpectedLiquibaseException(e); + } + + if (sqlConn != null) { + this.tryProxySession(conn.getURL(), sqlConn); + + try { + this.reservedWords.addAll(Arrays.asList(sqlConn.getMetaData().getSQLKeywords().toUpperCase().split(",\\s*"))); + } catch (SQLException e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could get sql keywords on OracleDatabase: " + e.getMessage()); + } + + try { + Method method = sqlConn.getClass().getMethod("setRemarksReporting", Boolean.TYPE); + method.setAccessible(true); + method.invoke(sqlConn, true); + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Could not set remarks reporting on OracleDatabase: " + e.getMessage()); + } + + CallableStatement statement = null; + + try { + statement = sqlConn.prepareCall("{call DBMS_UTILITY.DB_VERSION(?,?)}"); + statement.registerOutParameter(1, 12); + statement.registerOutParameter(2, 12); + statement.execute(); + String compatibleVersion = statement.getString(2); + if (compatibleVersion != null) { + Matcher majorVersionMatcher = VERSION_PATTERN.matcher(compatibleVersion); + if (majorVersionMatcher.matches()) { + this.databaseMajorVersion = Integer.valueOf(majorVersionMatcher.group(1)); + this.databaseMinorVersion = Integer.valueOf(majorVersionMatcher.group(2)); + } + } + } catch (SQLException e) { + String message = "Cannot read from DBMS_UTILITY.DB_VERSION: " + e.getMessage(); + Scope.getCurrentScope().getLog(this.getClass()).info("Could not set check compatibility mode on OracleDatabase, assuming not running in any sort of compatibility mode: " + message); + } finally { + JdbcUtil.closeStatement(statement); + } + + if (GlobalConfiguration.DDL_LOCK_TIMEOUT.getCurrentValue() != null) { + int timeoutValue = (Integer)GlobalConfiguration.DDL_LOCK_TIMEOUT.getCurrentValue(); + Scope.getCurrentScope().getLog(this.getClass()).fine("Setting DDL_LOCK_TIMEOUT value to " + timeoutValue); + String sql = "ALTER SESSION SET DDL_LOCK_TIMEOUT=" + timeoutValue; + PreparedStatement ddlLockTimeoutStatement = null; + + try { + ddlLockTimeoutStatement = sqlConn.prepareStatement(sql); + ddlLockTimeoutStatement.execute(); + } catch (SQLException sqle) { + Scope.getCurrentScope().getUI().sendErrorMessage("Unable to set the DDL_LOCK_TIMEOUT_VALUE: " + sqle.getMessage(), sqle); + Scope.getCurrentScope().getLog(this.getClass()).warning("Unable to set the DDL_LOCK_TIMEOUT_VALUE: " + sqle.getMessage(), sqle); + } finally { + JdbcUtil.closeStatement(ddlLockTimeoutStatement); + } + } + } + } + + super.setConnection(conn); + } + + public String getShortName() { + return "dm"; + } + + protected String getDefaultDatabaseProductName() { + return PRODUCT_NAME; + } + + public int getDatabaseMajorVersion() throws DatabaseException { + return this.databaseMajorVersion == null ? super.getDatabaseMajorVersion() : this.databaseMajorVersion; + } + + public int getDatabaseMinorVersion() throws DatabaseException { + return this.databaseMinorVersion == null ? super.getDatabaseMinorVersion() : this.databaseMinorVersion; + } + + public Integer getDefaultPort() { + return 5236; + } + + public String getJdbcCatalogName(CatalogAndSchema schema) { + return null; + } + + public String getJdbcSchemaName(CatalogAndSchema schema) { + return this.correctObjectName(schema.getCatalogName() == null ? schema.getSchemaName() : schema.getCatalogName(), Schema.class); + } + + protected String getAutoIncrementClause(String generationType, Boolean defaultOnNull) { + if (StringUtil.isEmpty(generationType)) { + return super.getAutoIncrementClause(); + } else { + String autoIncrementClause = "GENERATED %s AS IDENTITY"; + String generationStrategy = generationType; + if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) { + generationStrategy = generationType + " ON NULL"; + } + + return String.format(autoIncrementClause, generationStrategy); + } + } + + public String generatePrimaryKeyName(String tableName) { + return tableName.length() > 27 ? "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27) : "PK_" + tableName.toUpperCase(Locale.US); + } + + public boolean supportsInitiallyDeferrableColumns() { + return true; + } + + public boolean isReservedWord(String objectName) { + return this.reservedWords.contains(objectName.toUpperCase()); + } + + public boolean supportsSequences() { + return true; + } + + public boolean supports(Class object) { + return Schema.class.isAssignableFrom(object) ? false : super.supports(object); + } + + public boolean supportsSchemas() { + return false; + } + + protected String getConnectionCatalogName() throws DatabaseException { + if (this.getConnection() instanceof OfflineConnection) { + return this.getConnection().getCatalog(); + } else if (!(this.getConnection() instanceof JdbcConnection)) { + return this.defaultCatalogName; + } else { + try { + return (String)((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class); + } catch (Exception e) { + Scope.getCurrentScope().getLog(this.getClass()).info("Error getting default schema", e); + return null; + } + } + } + + public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { + return "oracle".equalsIgnoreCase(conn.getDatabaseProductName()); + } + + public String getDefaultDriver(String url) { + return url.startsWith("jdbc:dm") ? "dm.jdbc.driver.DmDriver" : null; + } + + public String getDefaultCatalogName() { + String defaultCatalogName = super.getDefaultCatalogName(); + if (Boolean.TRUE.equals(GlobalConfiguration.PRESERVE_SCHEMA_CASE.getCurrentValue())) { + return defaultCatalogName; + } else { + return defaultCatalogName == null ? null : defaultCatalogName.toUpperCase(Locale.US); + } + } + + public String getDateLiteral(String isoDate) { + String normalLiteral = super.getDateLiteral(isoDate); + if (this.isDateOnly(isoDate)) { + return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')"; + } else if (this.isTimeOnly(isoDate)) { + return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')"; + } else if (this.isTimestamp(isoDate)) { + return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')"; + } else if (this.isDateTime(isoDate)) { + int seppos = normalLiteral.lastIndexOf(46); + if (seppos != -1) { + normalLiteral = normalLiteral.substring(0, seppos) + "'"; + } + + return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')"; + } else { + return "UNSUPPORTED:" + isoDate; + } + } + + public boolean isSystemObject(DatabaseObject example) { + if (example == null) { + return false; + } else if (this.isLiquibaseObject(example)) { + return false; + } else { + if (example instanceof Schema) { + if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) { + return true; + } + + if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) { + return true; + } + } else if (this.isSystemObject(example.getSchema())) { + return true; + } + + if (example instanceof Catalog) { + if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) { + return true; + } + } else if (example.getName() != null) { + if (example.getName().startsWith("BIN$")) { + boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin(); + if (!filteredInOriginalQuery) { + filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName()); + } + + if (!filteredInOriginalQuery) { + return true; + } + + return !(example instanceof PrimaryKey) && !(example instanceof Index) && !(example instanceof UniqueConstraint); + } + + if (example.getName().startsWith("AQ$")) { + return true; + } + + if (example.getName().startsWith("DR$")) { + return true; + } + + if (example.getName().startsWith("SYS_IOT_OVER")) { + return true; + } + + if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) { + return true; + } + + if (example.getName().startsWith("MLOG$_")) { + return true; + } + + if (example.getName().startsWith("RUPD$_")) { + return true; + } + + if (example.getName().startsWith("WM$_")) { + return true; + } + + if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { + return true; + } + + if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { + return true; + } + + if (example.getName().startsWith("ISEQ$$_")) { + return true; + } + + if (example.getName().startsWith("USLOG$")) { + return true; + } + + if (example.getName().startsWith("SYS_FBA")) { + return true; + } + } + + return super.isSystemObject(example); + } + } + + public boolean supportsTablespaces() { + return true; + } + + public boolean supportsAutoIncrement() { + boolean isAutoIncrementSupported = false; + + try { + if (this.getDatabaseMajorVersion() >= 12) { + isAutoIncrementSupported = true; + } + } catch (DatabaseException var3) { + isAutoIncrementSupported = false; + } + + return isAutoIncrementSupported; + } + + public boolean supportsRestrictForeignKeys() { + return false; + } + + public int getDataTypeMaxParameters(String dataTypeName) { + if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) { + return 0; + } else { + return "BINARY_DOUBLE".equals(dataTypeName.toUpperCase()) ? 0 : super.getDataTypeMaxParameters(dataTypeName); + } + } + + public String getSystemTableWhereClause(String tableNameColumn) { + List clauses = new ArrayList(Arrays.asList("BIN$", "AQ$", "DR$", "SYS_IOT_OVER", "MLOG$_", "RUPD$_", "WM$_", "ISEQ$$_", "USLOG$", "SYS_FBA")); + clauses.replaceAll((s) -> tableNameColumn + " NOT LIKE '" + s + "%'"); + return "(" + StringUtil.join(clauses, " AND ") + ")"; + } + + public boolean jdbcCallsCatalogsSchemas() { + return true; + } + + public Set getUserDefinedTypes() { + if (this.userDefinedTypes == null) { + this.userDefinedTypes = new HashSet(); + if (this.getConnection() != null && !(this.getConnection() instanceof OfflineConnection)) { + try { + try { + this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawParameterizedSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class)); + } catch (DatabaseException var2) { + this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawParameterizedSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class)); + } + } catch (DatabaseException var3) { + } + } + } + + return this.userDefinedTypes; + } + + public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) { + if (databaseFunction != null && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) { + return databaseFunction.toString(); + } else if (!(databaseFunction instanceof SequenceNextValueFunction) && !(databaseFunction instanceof SequenceCurrentValueFunction)) { + return super.generateDatabaseFunctionValue(databaseFunction); + } else { + String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction); + return quotedSeq.replaceFirst("\"([^.\"]+)\\.([^.\"]+)\"", "\"$1\".\"$2\""); + } + } + + public ValidationErrors validate() { + ValidationErrors errors = super.validate(); + DatabaseConnection connection = this.getConnection(); + if (connection != null && !(connection instanceof OfflineConnection)) { + if (!this.canAccessDbaRecycleBin()) { + errors.addWarning(this.getDbaRecycleBinWarning()); + } + + return errors; + } else { + Scope.getCurrentScope().getLog(this.getClass()).info("Cannot validate offline database"); + return errors; + } + } + + public String getDbaRecycleBinWarning() { + return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where constraints are deleted and restored. Since Oracle doesn't properly restore the original table names referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this issue.\n\nThe user you used to connect to the database (" + this.getConnection().getConnectionUserName() + ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. Please run the following SQL to set the appropriate permissions, and try running the command again.\n\n GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + this.getConnection().getConnectionUserName() + ";"; + } + + public boolean canAccessDbaRecycleBin() { + if (this.canAccessDbaRecycleBin == null) { + DatabaseConnection connection = this.getConnection(); + if (connection == null || connection instanceof OfflineConnection) { + return false; + } + + Statement statement = null; + + try { + statement = ((JdbcConnection)connection).createStatement(); + ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1"); + resultSet.close(); + this.canAccessDbaRecycleBin = true; + } catch (Exception var7) { + if (var7 instanceof SQLException && var7.getMessage().startsWith("ORA-00942")) { + this.canAccessDbaRecycleBin = false; + } else { + Scope.getCurrentScope().getLog(this.getClass()).warning("Cannot check dba_recyclebin access", var7); + this.canAccessDbaRecycleBin = false; + } + } finally { + JdbcUtil.close((ResultSet)null, statement); + } + } + + return this.canAccessDbaRecycleBin; + } + + public boolean supportsNotNullConstraintNames() { + return true; + } + + public boolean isValidOracleIdentifier(String identifier, Class type) { + if (identifier != null && identifier.length() >= 1) { + if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) { + return false; + } else { + return identifier.length() <= 128; + } + } else { + return false; + } + } + + public int getIdentifierMaximumLength() { + try { + if (this.getDatabaseMajorVersion() < 12) { + return 30; + } else { + return this.getDatabaseMajorVersion() == 12 && this.getDatabaseMinorVersion() <= 1 ? 30 : 128; + } + } catch (DatabaseException ex) { + throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex); + } + } + + public boolean supportsDatabaseChangeLogHistory() { + return true; + } + + public String correctObjectName(String objectName, Class objectType) { + return objectType.equals(Column.class) && StringUtils.startsWithIgnoreCase(objectName, "int") ? "NUMBER(*, 0)" : super.correctObjectName(objectName, objectType); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/liquibase/datatype/core/BooleanType.java new file mode 100644 index 00000000..6f574106 --- /dev/null +++ b/yudao-module-bpm/yudao-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/yudao-module-bpm/yudao-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java new file mode 100644 index 00000000..2ac83d58 --- /dev/null +++ b/yudao-module-bpm/yudao-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/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg4.png b/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg4.png new file mode 100644 index 0000000000000000000000000000000000000000..2e3d8716675b8221790ebb92044c693bcd992a95 GIT binary patch literal 27859 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

%*OsvMc#btmp6<9Y`Cx*|=ZX5v|g|d5U>f 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 bKJWhnL=8P@ya`>$00000NkvXXu0mjf!i)#m literal 0 HcmV?d00001 diff --git a/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg6.png b/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg6.png new file mode 100644 index 0000000000000000000000000000000000000000..5024ceb2295ae68930dd6d1186a446f15d837dbf GIT binary patch literal 19160 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

S>ebBc{*9*Qx;q03YDm_y*;w1t7ZMelU4@B> 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 literal 0 HcmV?d00001 diff --git a/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg8.png b/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg8.png new file mode 100644 index 0000000000000000000000000000000000000000..2727aa32416cfcc9d40209d5907f4297d3f8101d GIT binary patch literal 30332 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 z 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 literal 0 HcmV?d00001 diff --git a/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg9.png b/yudao-module-system/yudao-module-system-server/src/main/resources/images/jigsaw/original/bg9.png new file mode 100644 index 0000000000000000000000000000000000000000..4463aa2fbd82ee4ae7c3129e6c5f9b36aab05843 GIT binary patch literal 26977 zcmV(>K-j;DP)Px%vQSJ^Mf>~x{{GMN`27FGSB<>ipzk%;E0-=1%v?^xWCvwB_;b$mPD= z?bXxU``S_8%;DzF#Hr-({@OnH)MxX?x$?a3>d%y&9ncFzsK#V%K5uJ(X+|@ye5Xx&aAfAtenQI!lSv*=&(bSyWr`P)aRMB@sYXvsUl+0$ZMmBiLbkMi!`gx%WZvS zt6Pp^d!_eH3ZYk?w^4bvQf8lKIf-D2iDYZfNJE}de7Q?fZEAT|Zk>-`NS#t<=r%E} zNNwXi7Nt;9YGIL*N{*~VUD!1=cvy6RR#~7yT(mqvOJR}2Gcs0Mf}lN7gHbYsL4lD# zV@ho^&nqWjR%?+zRi8r}r8hlmPgs;WOtUR7LQ;XgB_n%0S&BACcQ6=1FaPoTumI%Z z_uv2cV|h(u@QH?lf+(t{S-V5 zn+0lw4Q>c*@Qvk3WQxYAhTxlEbGS9qw&AS=dV2c$2I61us9VE674C%MpA_W(m9||t zlj7R8=!-67PBk`Bsd(8*?c0AI_T~lwjfBhRC)As4GX$*(Xe?WGNwwBI4;zM!bnVGg z#7yDXx{Z^nO|fyP;d_M5K~fbMf1R>j^*D0S)ARf`Hc5K%{uoMA?%h)+|Ao0O=AMbh zCzqLY?b*j{m!d(o6PlX`#7O+eBeKl2K2M7VwGxE7p=v-|U#D<3%f|APCuTNqqjkfS zGHTAez@Lfe);6|=#z4-&)A6ME-yS@8;NU^ljfzG^H|)znE~sj36PSFD3UNFCIfujf{3>N@pl-$cV{5scjw$_me`YjQk}8nxl5&YOpCyxE z65=tJj{NY03)Jl37Rk}Csu?RGNkQzLY-410n z=~0YZak8Ym{QC86Y_4dh{^7yp!;8=L_6q`ALWga6h?

P_nG7677>^8kTLjOJ%~4 zcI)~PbH7m!Dl(<>_>48d!8SRyyuQVTT@o4^8jCL#<5#FEzEgN*qqtN z(ikdftoPY%QNWLA-2ah`Mv_WDMRPF*+p`l|5TB2V1(Ef@-#2`zRE>PexIJaioNR2WxKWJ_4a}Qn-xSmwZ2P8*Jl%Ld zGzCKsBr3&n;Yq{FHS7Xbj8g589|OJO_iWnvFWkI?5WwNU=ItL@Z(DxCq7g01mX=l8 zJ;S2W{nu1iQCVeWm%(5#n{^I1GqGA;o+4#)MME~6Y&8-I*czxD+?YVEs;Z%iqygE( z&+&H&hpJiyryIf5ut#<+m?YEHjcn5X<&o+*s3G6~@XhtHTU~`T0 zcd7a)`(jIP5%^%>_tCR*JU)&@$WOV>ia7%^_KAETcNM$aV_d}B+uzUQn17HEXrtY= zwWTBkja6$J0-7UJgF0nJMYBPbn;}$nJy5ZA!xJ$QHXErM*nqaZX{)Qxr>A#jL#pTt zO|eu`Uc67X#)eX#g!_Wf(~XV$n@!i`r$?n+Cv!<2A7(ol`+9}!Zu!YvKdyg}pNCtR zu-=BKlWK^muD<5FL#lv1tw_(=}DwQ(P`d&M!tj;dYO*-MSr`s}bxtrBw*?A7dvnvXd?U@jAiE z&&R8am?KU*>~23l;6cvT!%ko!aOa%B$>qFJOAl!UB$TZHG00b)#%boxwQCOzDSc(_>>}kH!`jrpGunReP&}tvH__6`ztKLGttS zi5}TId}w&D3aq3~%1%L9I!-#-a=RTH4wwjS7s8hU9y-luOCU znJAm2v-7sR8f?imJO4nWyvl{lH9zc?tN8f->e+uBa69IQkn?pvHxB23N2a{KR@<#B z#bBnT36NMpR#SHf=Wj><@0;&rJTuUmBLc#cNb-`BE_j{cRyH@Q)W&Wd!m^RNnLlLP zMAaURO;0aOFDxuj!fF*P5rfzeWswe+SRm;NF*lvQd=D{r;jkxHt_cvf8! zCyT*WqZ;Yd8g7>gd?HcD9RsUkhHPJ2u4Gs7xC*o{4!FBR?Syv_AM*(`zry6OTXLPz zsKaQ|BnnDvhjfSjb`)j`*{HhStSdR=6%iAYl$4Q|mvrWgmq$i(WnCS*ro)qEYeV0R zbS-7wU^b3*0I}N_U^n)NQ-$naG^&z^yt9!mf>M(bb30zye}>K@9hWoDI8jt zoS7+>G}PSr&(Kik(CymN(38atJE9dAmlq@Z@Jijuj0SM$zdGQCjhY|!9eBEb#^D?j zcUUysLV3reQ7H-y4NXi;Oe@9eITL#HyYF&j3cW%>W3QgBR031vGIS*@he z(HPk&ZEnLJ1a-5mZ;h4)*rV^@3!7aza*4O#hK#Fs)lmav!RjSI&atB7WkyPusMF_{z?`dc*TW` zMf>(EH#caVz&QbF+5ry7L)c+b8EoQYxx7QK&Oy_1+S_GXEC7mcWQrV_Os!UFWZ4;s zB}p+^88P8m6=YJ=2pT!bZ_;5(m2Ba_)~xGC-Ci(bI{p9w7R078po9=?JExdQzN9!m z{%b;pKy7$|t*se#BUAd~#p?Rz*22#Z9{k)j z{sv$ zvk}6U1GbOFT#6QbI?8nn_YTmGA9nY{DHPCfV!$*_V*(l(GK=`qE%~HT(LUNP%gM?4 ziRaEa=*{H|`F!EEo7e6>z8@AG5RjIY6q6Ja>=jW0uO?i4BT0x*rY8jR)8%o4lg3Ftu3hDopkAd;aGBEW^$nz zGg^LUWMt^&2m-dyl$sC4kdFs5HRw#rzPVVme_=eq05Y=W_>6PFo!?>Us4USLu@vZR zW!{TuC^$nWInZi2iwhFAfz#5wd;DhmLu?HUh%RL`xftUR`NC={}^w4|7rh{P-e zZY8-TNOg0Q4mTH`QZ{Bx2MOEi`o`ue3I;NS9@v1#*~N@iOMzNT3Jztxy{(H(tfXvf zD~qoedt0S<(hHZ%FrsDRqFYrGZ8z9xz$PjG6dH0h^{=3v`qIrKQe2cQ1RIWO+X*?y<4UrjJB1Phu16$nQSyIEisKr z7A_~{iilg2){INOEE_`DCZE2rt*<ZcwxZd`vT6EpWD>xpWO|sVyy%JJMD(Yj}l_ zAuHCExs}D)wbv`dcUqI8t zg<^3sAK~)F9X3;$pWK2&l2XJ|=9~=S)oRW9_V%Apw?iSL>IC7x1c%8cpk#LDczz#0biR-Cxdf~u93)fc8n>*{n`Z8uTt)CxlKcQVW08STm^ zbM`AYZ@v&k6XFFa>#*sK%6fkCfPvif6p2E>meXw<#lEAx{itkdX=zmb@ZrF~#Kf%S zr=Qvtqpx2N1ka7iG;b7WTVf)f)J=ekmreao=a(tlyn~I_ZSpy(+Xl8BV?d+9muQ<4 z%0&%s`Cx0sgkslWU9k>B46EsE&%|O6?#RBj4&O=;iw8Q&>J7#xb-0sTrmRzoI zG^P9_Gn}E*U2N)KBty>s%N?6EA(Lc2L7OtxR+p9(aWxj5vMD!&$Aep~vAum1BkF@n zE#V9Oz`($qeQjf@z5VyCx0`o^?MpoQC@(Sb@@2q*jh=*+;EpLm-R8{>Hq>o!@`ddM zVOu2?TVUAmuygiQqins%Fm`OS)?VxEgw^_3qnYT@Xw;@PYj3MqEH3LXJQ*7Lt&YzR z3rna}>bi$IJ4dv<(5Uk3_&LC$VWtyke^rd49rgekA4wJqh2kO=K_gc=(kl6J14eE2 z$&+m7~8#Zej5mh{+$j6!V_+1h&d?%n3K@XaNWJUaktB!n$Cv7pI3GtY!v zy6n@nQ8q{2o)fkWux;3JK1$qh^@y{F8rV{y1~dA6JUcu*@!0xUUZ(k_tiHaZ!#>;F z+ml^ZrqW_Ae@lpjn_mLhI!8w2bvc44^lMv4&5%!!efupQ+j9lCg52D&_hJrc7*N_Y zE!1#GQs;_;G$tj;c-6HzqFRO8IFs{@Os^h=8n(Q@FKqzr-R3)FYwqUVttHv9W7LfH zEjBi-T1W2|5VQG7P$57A9==B1oKrfzYPZ{037Z{kd#I&iNLxtNm?!G?c%Vm~ot;gd z+4UWk+2Pj3o-)-4oL+WEMkXW(!-6Vx-6KYgN?XT^f|}ze0gOdU#m7ffd*%G`!%z!i z)VM-1(B{n)4N>B_&(G9|l+`?5b(5CtL9{vFsEJy;0xQE3PK`E6h}!1dgnetYJ><}3 zHnEozOSR;73uT)!7@Bdpx01T`_0cIckh-lBH~Sd+20f_l#+L77lSn`YvB$%$c$w|d zB!uup^=iGzWKz|e=5WYc92oh}$g^kvtArYH%hu^MnxTIl5>*hTDO)Wu!U zJiKvsgg{MP-(j<%X|)usv_Pp}Qu_&cU=xXQnzXe!T5afe?d@u{0-ma&uO&4gIHcMdYf{V&v%k(vz)WfK+=wkJc{TTpYFT}j=I%&CNK9d||Zh8cw`v)GK) z^rlKI)X+9X_Mt<(QMieY9_5i+0JTc6bt{tx=H~2ByC%F=bggJ$166axk_a_Ap8e** zL8wtQTzZ3B-(=ejxHY3^l#Nbl-{ANQwr*hi_rFm#JO0Q476D_1)Ab;ZA;r1qT8|7` zyab*|B$L(Y*Rh;$nN(`a+^}_FL|ZKisefnLG9_eV7k5*s?H)49C0Ctf$lhkIIZM!B z1~q?wsBz0WObib;Zmse*=~nqIA-vU`tWkAi5Z^B19oKK=cIGzg2SOkOJ-ZA zYW6g+;p(NfpMIu5aBDX7VM-lredFWr>o!Kp_GlVE#?GNgAp0niR1)Ax zTdgZAbJ!yS&6wTO!~SK=AKGyEW5o|DD--D2r1ks&&P*z$k>f_RCH;8pgp`6 z>>GUTE+&+s>1`Xx=3Fi|9R)z6LErk@@?yfnab6!2o|M;YngrVo+;T?11~+^L8^)BW z+X4!Qx@{~hP&Nmhi(CBH(&5FGm9-AF3CA?v$q=+n@#bC6+L~OmxwW=}?{PyS6Czc% z=N&);TRg}Zv|Y&%S1f0McG$x|$lsf~d3gH^>rI9_oH!Pcs?h+gpvkC5-$vDk4yl*) zqR>O1sj738v}2~<(@v7HwQs0nooGm3Gtgpal2Ew{m8m)JM&6AZKlc$f zm%2fVE~E(9c1&uD1lNmFE(WIK6#n(f8rk?PDMTkjuwl!2W0_c+D3sea!1j7Y9FmYA zPEe`J#BjTr9v#0=F;_2V({Mmz*}S~CWd=hv)&ZC$#xhC7GO9CA>2axBuh+{$LiD4u z5Vx$6Kjhqj4f{jB=RvO`6pfleYxViW!~i%;(zc_uwUs2EJ9jSp9MF=I(7B3ALt7F` z*VToc2;6{24{l&%+}Q1h9sAy$*~oadw6;j4I7zUgY-^iqYi~Eyypz0atP1LETzSS0 z#4<6v_ z=OBxwYlE6H#?$xQIbYv!1lP#1Fyvvot1@&ocNyT-Pu)JO8^#k)tfgQ>T)HNqdtIb; zco=N2H+yE~*-Y86XCvVylxZG=bVc)a&0^}%AZ6l9+5ejMr#G2T zuOnV!Rb5Gz!z_ZnvKua>Ex7N|IY|+T;%i_XLoCpg$5f#6W zga1cjr%oLQ8oEZ*NZI_)M3$+PS!odwM2x-?y{snPpm`868x<&;jD*N3+W%C3c5LX4 z22Ep%W*I=s1|}>P+@2`_n%RLCbIvoMAn|f+01j-JQA}ta)`mRfhQVNHGZ@Cl8Mc0= zZhelrtx`3Fg98vN=OCkODg2apvXr)9O;~w5N3eWCMHo`HY~q`8Tus9KzP0p(7&_|3 zafNZ0E}cH{_b^qZ^gJu}>5_fThCxI8X}p5O^#m<3F_O!Th`4a!0yCeu-VZb;IO$}w zq?ajvePeNpeD;c&PHaJXdT{svSyn9B+@hTC(E>buFPw`>%ZoX35HlJ>*Z_vLfTY=g zboF(0wY_(HK=&ZyPBzRahV)zF<{F7am>f@%a%XsP?d=4lLIoltZq$mH5iNq++zMfP zxBBdrt?*KEW?a~%)2H3tjIUnG;k17<7R}wAveDfwK`Tv5L}DZP+zS^--LOs7^$$AI zS0QU3)l;=m#dm1iR+!-B|1@iJ*mu|`KxR-@my+Ern&v<&i19pl&@&+7a!g``=ecu7 ze6a(`qf04ut7v1|2DvuJs<3?zf)gt{^j0}2hO3(-HC`eWrx3R2R_iQf^YIbnUW?WobbH7L$T)Xu6Wi0Rg@T zJw}l4>V-JFz8rt^!`8eZ+;1gXHp5vwh-Meidc8>SXZjyM( zt#ova+zXn-JuPN0DhBtok+SFnnJgo#p6Bm@xD z%H?(J3?kwij_3cl-?g8I7R1RpU!fv85k7v`UTf{W_A~I-&6`{@JqXiK$;;X_U`vXN zi2VnF;-K*&Xcf@4=j+ecclQ?bS(%w7H-;xBXtT}O^sve7U8eItVDtHR+-_4j`HezV zqx7tAE#X@`tYg3qpb;<`ThnMdz7o(b0a|v5S!(tVSMMKj+Ue8ZLTQ*sh%fA>Mq!%- zzhw@M8q9A4J0BN|srRJ)4T3gxv*=Vrcz8ryD!iL|_7vz5w=32vL@D3{TVe*Qd3zDJ zm(k15&Gin1RyW2C*m%2burA@dwD|b6e}LOzv+33_ITK7 z)EjNroBDm|5eVCdoq*Tv!0e2!9NbJu3KbQ$CEMvQnZ>e~kQ5v=NEt&)D5&jhwerwX z4yaLp4wA;1)4BkS^liNCjE?;93R-#n=W(q^v;&nhWmT>S)~G}UA;tEAo0{G zyro{akP5fq1>AtJj3omsYt$;B-euV8uM7-a!TS+xQ;R{ny#_e}t=<2~J&G+iH~0LH zKUPq0b*;U9hdXHn`YDIh^u+W_n=s9i{;g_(Kf9ok>1GF4m@T63L))Q|K@NqE;QJqU6 z3T6=VS{Za@0BhTo`l_z+klolx^Fi%~j#t1@%RNl($AA8qoBLx$#mKcfnsIIiw!J_5 zvnUl9v9i&t+v|P4v9L~=^sdtU?me3LW}C@@Nw@NrnTM0BW(>rorE%mqROy%UqoZRL z3WZ!QA!wkMpd=wgaRY;cMv35nTHX&07(E^z2Q>~_(66Us9Z&Se?d{FYkDp#o+F7$O zG;~5XG^Dp@4nKap6zFUsY`taD@NmK=m7ay=2p@7OMAR<+a54LPw4(s|Jv>)N>^OY& zU3Uh$>g%iOZ;prD25c+5-9U}7@xp=z;U}ura&zm>x7Ynxk&7ux&-yOxwrAvBKt^=!Ev&{j|IcENN?%nd%aZ}~7q4htxNCTTC$bd`+R5B$AR#T>z z)-q^$P%u6T8u}a%Bi-!M#RGbfn``K#@$>#Ra_6$Q&BD0Z1)1BkndZ!=D^ItE6*?q> zdW2_01aRRsF$K^lSdkX8Gcr;$E|gu&ON9aBViM1y(7lYz0C1E_>uGlKN*6`x{lg^) zVSB|&UR)Cgw&VKoe{%nMt^LREa>+3jBkSOXMnR7qBej}5XR$lI3kyC3>H3F_g=bq1 z_uA{b4uAtWIM8R>TE5l}aCPlzv7yLRmcn%xP}zLXu%XaJTF$`mIPHm3Hs}SpuC4(PW7zP6vm1s)zzy$T z!-jPYV{Prghe)G=U2wx#mPw!Wjrto#4b{?u57ImxJdy*`GGoEB7Uyh?Ne zBY)c{)P{h~1#FGy(98?M=}ckP-oN#v(gLIA zWY`w)`Gv`a32pz1?*n@Jy^S|x<+_QTeiKGjSrYbm(xQ(_o~}&?Hzfmy@HAWDvyjmM zEhR3F+WDO7=1$yMywg(-Xd$W8nctk#8=nkeIs5OS&Fu+LYwYOWHVD+)n?sqi_7hL% zo;_+UM)x5>fg@MMM@5PwA|fR6{d-%galN!|wqc zxK;LGzO+0(Dmp4UsK^ugZp*n@bf7dKiB`T5`l<2fZh;_k6WB)Wg4*(FnF6T6CsH0o ze`S;~To@T8$FSD&tExsI0!e9@vmgbLxA05u-ZgpQ` zQzA+eD0x&B1J>M7a56lJ1qna)L=r1C__4h&>_@!?iq@tFMM@J?Tjbt3$x4b`6OH65~!Ef)QfaqJjcFFr$z~ zB_idDIWPmaG6X1gjerc;&?od#xr?D+;znI9YfdrByTa^7*bX&}v^9-){xcQlPh;1p z4w)3AM&MS5Pva9aX3R5yn-`i{Sa)~+I&vKi{|Ju_J0cKA4+|UvhxU!WeEu8Il$K)D z7wvk;Xpl*u3Kw>)@^sP2~xH-bQRE;Cc2+& zX6n1mf=vHbl9<{y31T$rsIbmKvrbz{ zp;m&%Z4C|L@&Mzu=r9j$n=LN8p(CJ^gIXN0eZh>Z7Q_Z*$OF8nX5$kA)w?|yFmLXG zs(Tr>DTa;g7Qz-A)EC^+(&|P$@Z3X|6lQ=et$pU9LvPfx6%N>b{q?%Tc74R-p>(@P z+6`gD-34$1IC9+i!XUTkLYv!N#b-~5IjyR-wKADOYhaU9#0>>+e>}g2Clpk7Eu$Y? z=(IQ+7&USlpmAQCqm2>!rpwZ8mfSEZR51{+r7I2_4IP7&L8C`F;hk+4L10E=yB}Sb zFIQpWrn(uTIrBM0Dea_8fm;w8>bBUw6e|4lBc6)1^B&I(ajQTSpZDBtI!LG9^6S^H zx3si4o#sLf{;=9JM$I(5$UcC9-F2!=;OP5Su@1 zY#Z!UaM03gt0%GfurO)Oy9l5hwEN6yi*OpC#o~v{9B=7XCkcxaWDv}jlYYP~Dl!}b zHoOBGuq8_Alr%1pt2{uByoOe^8qBI|o11H!Z!N!n_ZzU$J_@)oY%4)*RD}{Hyih%P zM1`J;!t-_T64p0Q8a2`GPCduXS*$gATCST+j+u4j&5`z*bxH+fwqQ&Sd!^Z5^9}j3T1=ZD%u(2Vdwqq69Q z=ABFlmk|^>d~`yf2AhEyHCcFJb~_t~Rtra@tQSS4TUG|2RYS1ai#P8u&pSm?3cDdt zf$hL<0^~Mj^jE}J_Z97E$lE!ifl)@9# zdM4kzddZ$U1G_P75L*&>Y~U7xrBExPj>0RfC}iAb+GjjiKeWr?h zE$Cs;nu-o^Qpo{bs##g_n$ieZJP!`o6k30vfC7l)rU?Fx$Oh~*0lg`4qaEGE#F&gK zoFSm*C3HEs)CSR2z!!kFijhII2Cy5QvQCi}L)ebyXmxfRl{<%(mKK{<=b7=;VV#TB zEc>z`y!eO@HhJ-kzL>rCEU6l z63Q;*(3ZOP!dQ*XhyC#^y_$BkHQ3N?!S%l9w0e1VD1 zjI_re{XP^0t$pCu0A^JM8Wy=q+V0OPf!$b;R^?Qe-g^HY(5!hR%CC*XY-|RqN>Ifg zSM*>a95Uvxrz`glxbac? z>M*uQmTpKwF+M&@4qr`tVM|3}VZ2uBa%N^_Whx~i8O+A%?(^#t!%e$|(HbqJ)44&- zz4ze&8w`iam&yA~ zF(9oFHs&=%Dcc5N*dmWBR4Rl7aQLpZ+9nrvc$F%ZhR&(VO}5T{@5Mn7R=1s`0jR?B&2qzLT3Ng$CFO3Xm^L3q-=K_XY(;{~&E#thT- zefL)Y2zrWC%G+%bc6)UUn^1C6C_D~ML0=?8M^We+%~xtk8ZCw{I*XY)+B7ytu|}&3 zmuS4W!wGDR+n+&f5Y-|c9O|@J$c6A&NMcSx5hlKCfG7ny?bgQY$>lNY56ozxv6;tH z1Lg4wPG6-K-4W;nH3gs%HhD^Fj0DtpY;lQ+5h8|-Hd?ata&oY_R{#+-Y{CN>bhWql zDp`%`&B}q@;B?9fDBT2vNf^?Z6Jf%ykOMT5hF~?Bm0As?)Z7R>GiWp_oCp{L{%MB; zb6VkHlH%2;bKZXU9#tbeHOizsI5M2aqY(;^V-uagl&1CK#cbrX=H=H5lRj4oe~sYk zVQ5iu#n-rLIBbgHfJy<$Ng8mAi4cXbVQE7~h#+h+sI@VLjyHnV+z)7Eva9&f)^_vp z3cK?6+l#fe7gOxXWIQ%-12&oPh`uS2hVkHzqSL4pDixrCoD9t1EA6Aebg`C80!_oW z=a+0IRhU2|Y1&#^S{iik61ESVcx-qjzhOV1F!C5Tp-7#XZ?s;zNb$)QwMA!E35qqE z0GZ-7P@sqe4Ny4_POoCrq6UY<6j7i?>QqvIh6G+r5e!>$PI@vk8!n-YjWcKs4R~v~ zdY?Tk_T_JLcgDbNk+4ye(u2vqhAk}aVrql{+!(o#eu)@QIVMNVfwr1xb zK7Yre)<5CIq%#$_L1;Z3G{)^Xxtu)avM61q+?kIMg~V$Pp+pj*CZa~3Ng~BWHLyWB zr5HRJ#0_WwgK8Su#`*mL47-hE+HGwGKI5Wvk=c!c^eDFQ)Wk#q&kf|1Dkav3e5Gvo zk)gxFfN|`!eg0`PM^Pe{z=nb_O(jB9rc4L3tu4H5?C$a0Fnj}Q)H2ZiCBbWvANo7P z!Xc4bjeA@97;8F_+4<(R&(-HN8fg;}|HO>h7(wHx9rYUzP7$AzqhZ`6#=tPQvMIOU z%f&~i0b2}_!^H=LC(Op6fms{J4A2Ir7`4Tl7`4aU$bma^W8+g4r9r#BS`&o6_%QZ@ z&LbkoO{`LZQ+~iIG04;p^uWf;==WPSyvT62Wn;QZrBcdNs(jqZou8lH{`~ejwl~Ra zz((TNI)p5+F}I27Zv-_GjuH*|2+xu%%!3(6C!DwucZsOsJHXdE)|vwV@&=L|#n+O_1Mrxyd4rq~XNsbK+U<7&qXEDIG zO4t@5di<_{h#i(7B7(4S+$1V+lUw};nM_K`PC#`rqv8?WJo(V=u<;Fl;azxDim+&I8a<382H+H2GqKQHIm=lKBQXehE8u zJnG}1sd#BkCik;>|Fl-4)2aHm9?efX&6(;kS)?Q~Qo8_bTy6-KzNpJJPHS7( ziA+Otb#rUhiEpy(6BckQN~NygC+Z7cjuikJsFCFVmRyu}c5Qp#tHK{}ZSU`E!zm5$ z)TE$BcX|<_34m7B8iWQl^o@fWbdye*>lwDiIl{KM!umP(!ul%!I>t>c;yz2*lt#Zw zB_n0twx~0;N)4dpBMA^Qo}30zTI`(;1aRq## z;bV_TqEiNfSD_S&k?W-S%{?e@-+P@FOuFJlpOp=4yxn?;S&-Ym1vk2lYs>pyaiof_ zO}fI#U=mOhfgg?%8MfpY%s91Dc!r=2j)B?~u5-|F!WE*1AxfMyk3W6+l5^StwwGVQ zcG73UXJsX7yd)`W0{NKX76IF+%WT&gANlEOyj2wx7Cae#Dpq9<2byfQ;-#G}my;%J z)S~pZ;psk&=eohHHK86s@ylvB$`7<7TnBQVLUAOjXj4yh&&KB8Z0g_H8Fg9oq{avSXV0edb>}dAhS`u{MqrO&p<^v*q(!bsJ6~aH zp8WJ0^WCHPqekLZa*@Zzwj>L9X2-c5Cnt~8ecoT2ouwgiOgqrXV7e5OzJ~mCx;6-G zls~O)eTSIg)B@2tHwI`li%Jm+Y*SMRTH1IgaKvkMmB+Rs_>!8s+m%Ff%a+@>AIx_s zMM=1iVAZQn8ngcPcgx8qfk0r(=b8^VfsKUHgTT@C7nW3Yt2?Ws%xuQn^Yf3MZS|Gg zT@IUf8|4SEahq=>7OJ=yJ(8|NmwM~d``?$>_Pa~IK`W05;gX9Gj}2{3EyE`MPq@h; zA3<}CZW1-7d=$3^Yd&}}W(GuO2M>*2t*!k&-`e7xdK$CGnJKJxxxV)fprKmh{YCJA zjeh#lZTN-y=W|Ak$%_Qx9gpta>&U!u+wTv|`_I{(XHK58pZoZBtWu}Z&jo^Ega}Ex6mh z{N~-7&t=45bx8-jhOIRo8`ov-$ZgQdF>C^X)^7Q^TdgbC z;bT$9y?gZZfUV)-t>Kg1`v3a-&wBfsTZ0a?!@t$3k{a(QjkX|Z77u%raHZA$17 zL>GSu+HI;p9OTA)myjg?O9^clYZwAGCgf7aEx|DB!lO#CqTRp)G1_W40UGUnRMuj= zsUIf_n0uo$g(|3baFE9aXxImN30|DtzQks*;8}47L6TMz7A^{t-G1=EzcZ0qH;#^HouZ(l`UHY&U88m0=@l5bQ?W zR!A@5s&7P8sNvmwjvhF&(x%mbiA*U{K0@1BY(NyA?8dW1UFXg%-_^;JI{bm9av3no zG)lxNvm5PKx_uMad)uc7adtey8o1M;C7do40Xnlw@36aOaJ)d`lEchoSM}s4({|WdPhf}Y;Ha|uv&OO z8y2|z=R0KK2)(2m z{xOFJox+(D>T_q#*f)1*kq>OSbdoUYhh#LmtW**<6Jc|BD|NcccOO@0C+7YBt<@xr z4k7xp`|iTfHl7xFrc$Vzq282o>fXYwuC7m?U;n;LcYj<)BU{+;Zq_Nq5rD<8@yDgG z8=~~6fDAnohfWrkpq0?xQ>0klIr@YXS&eATu5sKpQ4r=8)i7#c)&Wv|27{BBu}7ia zpSY6oeGNTOmZ=Zi?qgZQobzwLf!W-P7mJ6mP5g4BF-fV`ufF}by87I_-aC zKyp;wn1425G*qH44-`BYM`MTfCs1Y_xc_Hh!&KF9u0ga_n zbPC6#lnKNzqt7cjxgU>kHe=7!J4?hii5lmzO)P2zaC0cDC@(d&2-rHH8}$EIYasiz zL5*!!VYj|AD?!7rdwYRxaBAf@5;Y2AxZ?K)b_28(4qJl0Ln#)9x!?KlAdRJ`-eQ$J zAFY7XkdMubc?cEA$NjfHx7)w7-DJ=x!v#bQ@yRYr5jGa1ki)mS_4cDfXxQ5F`-hb> zrLNLuYQp2d_HlIpPmIhLDq0rCZr_`p>-qn(&c`R}E8YK7N*&<~h944Gop1pIg&>0| zUx*H1Vg;vsO9=tmOheK;0Y#D<0-A3(I$Ydr@#`am5wv?8sVyj?5jD zA9nUXFhH=_Onepp4dz zsTPFy4-Ud?dJ93Yfm?a}PHYLusZ#k%Lj*SXUBtvTOOXyxMk=|{Yn7$waLukfKL%nK zF~Xf|s;q1qr1yXg+z_iI2&3V4Pf)x+Pii>phnf5j5U@!$5E(p;#c4(eYZtYyQFlAn zZ%r-8g02=zT8lRn4uylk%}5aIxMS<1Ifi)32w{3pZ8O`z01ke;mmUAd5{4~XSPR?Q zo!(I7AOC!FmHLMVwhK6on(!d!P&fVBpe?UCl zr|s6Z`&TliW)@bE8#*E@^A4uA2&QO&3Nz7k1 z^6m+q&qKKexunSk0=qB&`t2tSQBo^Cu6jPMeVwW7hCj3|ZkdJeS7aKoyIxs|=G6f@ zDaF?K+W-8=k4m)K7P7O)(v#j&GHAso9bmU`Wm$IV@l(V`!DQkzYybML&_!YIu>05t#!Ang>55N2Ar6{#gHE-b7 z7JI(FwF)*K@2LDp<wXHvt-Z#kSm-Gyz%;DN}r!u_-)bF9tF)(*m^}F*^P&&T-sWjYO86PN&=D4TLwkC;jBMISgnX#OE~Q z^L59&M>IivBjb^ey3nJ_zeRbpkajL4qoXMg-u!Q z=CQZm*2I8Lq`0Nym0nboo_?ghq6~IBC)mJ@!l1?*EMT;+(Z8#)!EeZ=c-Pc63^iQt zh-YVliL>z<%4%)taDLa413t;;!+yKt&X@`1MkflkMmP-AVtWcSTMQdIwr+fRV`YOG z#Qk8#A6afR0UH!qlK7nd1y);I>Og>k+9guEbo!eWwLc>Qyf5YS&4o5_gFaG8;aqC` zZ)D6CvAaBhuFGzUaywfDO2nms#gv}Tu*H9KQhL#m%2P}V!i_mqZ!E%SXd4H%>pV$B zY+^VB>T~V38VfQh+*8v?m)W4kMoc#x4p$3CyW;+!zr_Y@ImiHq1C!PE8V0ADyOWwW z8wG09Km8rta4A81_o#dH>C)PAdrpaSiP)Al))sGJP{X|j{wWV`=K5wkH3-<)k4evA z3mW=zs7JWbZgj)JxZhyIW=d(Ltfi$I;X3&4$Q8+>SEA6in{Ag?3YDG?f#Pi%6Svv;p(W8~50q6=T|r&c^l}+4d9la zKNq*5i{I}+chvwc2|%!so4r@4Iqeop?O1rkXt8xnjae}M=XsWDOWJF4ic5r-7BIJK zv$^ROSZ8HrsAj@35s56iyX`gQmJTPNU44c-bY8nHroPZpNUSE*uAJ_^aE~_QI7l7E&-K3|X8i!C*d zNI3MJrUu8EcBf&^CI63l`(AV7WxqYdrtS8dC`Dredr9Fom<)$O5T?u_=`mZWa3nAx zwiTlVWTnxO+lkZUhI_r2)<(D@-qyx19Fc6Z_FVr8>d}SBqi2hx25fzP2O9eZx^1)# zgm!$V!AZ z4m?rN76HwU|FH>L1qOSUBR)U1E0!-%QLg|Jr*e2+WM|vhux^J zh>gSBUvA$KsZFl;S;dM_59`1vc+hL!>R1AAf`W+Vg0I?xTt>hMU8_kBniQ-P^ zjc?zN_S#IyFAa+VuD_H9N0?Xws&bJg8{6z*J~LCDJdDS8!U&v zpGYZWX$`y9Uhyt*nKK}%6__tbXAdA&%Z??6=Y_yt~O|T)*)UlX$EVQ~` zAU+8Ve9O!Bz zwp0D}zY0={9rJ~$rS%_`W|tMFgTY${z}kYQ`MkZWz+6?;&`{M-5XFX9RIKC_x0w+T z1GP4x##tY~i~#Yy!V|^R&49!BbCU6B2Msu$k;}u?ED<9@uUS!EQ~R=T4=@Cun5y zCW7IB?PG*!VR|C$BTh4b9a%vIyXdzne5wMus$E*(ChwYClF^vXD4m?_Y-_8ou(o}U z!42U_T_?7`$k(IA7cT@`vtt@pDvaBt4cFP6Oc|lLi`PnxaV?Hol)g2CJ{SCzW2*5i zxv>3%hANi}%LE>e7j>+X^Meb#X5}Z=5ZGDy_S=VZ(~oXFb-V5Gb@f~&Ydjz zy{LWBkI$NfNdc2woPxGh)_>M?>RkPn*>Glx;^`>{YHDFwworpW4cK<=0y2zf<_v|V zAQvLH26IMhL1Km$JV$mqP!A7#Jd^joM*Z4{#UbU73Thl4r|KW=GyOcmbGt$OEx0ExA4@LEDLQZGMi)lvlyu_<#Qw{r$a& z(oBXXzJLlQ6R%-@l}&=BiNuz;3lYkKLqRhig1Z={;ATE_=;+a-4Xv%MRro*6*e2u# zP=ihY+a#v&m{-ZDrtPM&;f>gyEdra{*6m&$z^cF7X$Lf|xTf{+0j%P_lh!{niT^~O zFDN3aSt^KS?Nr=4&;&NT^eIOkq^hi>_tN(yQmoSGuA=mm zf+{mCmZ%Edj6))DLuN=6?Esg~rI8doOa@V^GJ-e_+Ud4ucmp?9>!?YCvvkiqxj=OihuR4pHoqC$l|<`Wcs;5!%L+dnG2d z?s;|kYOfumWRHA74_<>B)9y0Z8$T2LG7T2!42RpO-|nLtNw&;Q!^{g zO_)6bHhxq za7JU&iAh8$(kNk}*yXp+g&VO!&%|=i4WQOdv$^eQMlEf7I*;A!+O#HYCj=0a zvl?4)on|ApsgTBofsJn4dx2I1G^WT`<2Qze?mes#ZaFpXt35n3VaEvoKC=+FniMZF zyM*6J0!1po+r~zeo5U-*an_|4%pXGIIw>RwHd13cfQp0~Xzba8diSlw#4K|zpk?JB z%9liu#aLxVWt3)+SxhNPD2idY)02}Ee*iTQ(_&80*!Vy-epoaJHB0xn!xE!rL_RPW zP9PxH1HLhIFNg3^VdFmOEZ+I!EU@`ym)gG9#EY>^D?fu6p}k(kF?wRVV9Jv)#o2RU zL+5~C<83|3t|dOpG_nNKL4(V{he#zg0@KU(ON+!No%s)`o&+_C$2S8`I^z5>seO24 zU)G+iJtB;5a9mcFSwYL+M{0Y>PCSMnZ8h&0*|K0tW`%S-F*n_KfOeDl6!aBNu1S#M zrMdC;|7XC4DilNZfPE`O@$Ykl`2aT@1QDFRDRcpb!!c~kq1mz4!<_yQn&?a4?iC^$rD`}T3}o`vAW z4+zODCes4VZ4aO+as?dRk|dkrR;e~l-XYc?eL$wL;l-3d&l$Q=#?}nD9ko$vaY?@Q z69)96216kqg|R;p1}0J?u@Jx^Q>@SWwy?PncA3o&-UzhKUw&Sp;5W!(BDP`+4xa;C zdAZerQI6`gk1j56ETav4aQBeu9K?xt-^^!cdE?9s# zyLuON;J1DI_Gp=RWtc&YFtg}03~c-I^ACYuVimAeZDHF3a7rxmk_6n~%*1siU=y!V zk`PEt9A~IOh}w}xi_?;~%yvE;G6GvtFdP7x04Ls|&B%I?*rphubR4%reF5*%f?&f9 z!)}c1g!UR8^(zl~G!5MFKv0v3*vjp_@R{{6Y}PaCc0l(>`kc;DOoAO9cyd68!+}kk zP{8YXP7xh8kI(};tqMWTRQX3S-j>R$EkTKe2Rhfv3ifGjQOihFRwFkKc!Es@thu3~ zwILRzF>X-05#&zKa4!Lhj%)Zpr-`I;1GW+imfP$tX~3pOwz2T12FU0O16Tl>BDYOx zSVK+-lbUb~`27AE3DRo!Yn2f>ha~je$adn&=8*ThCBZGAt^UEe_wuUYXfQy zglYaEGv25yNeK{a25$RU4_7sCAfr+;Qq<5E16PEkP~@4PkX;ukceUs@8V+ydX0ViS zk+R1U!**_c*dQ7X&k`89-B}O+7#u^0GB|?($XEq8V58mUpS%%mI5LN%)RNO1LJh-P zJGjAaJ&e+S`jRpiTi?fWULO9I}0q*8N!e$^kN)DD@=S%MAFpe&Xj3{Ro9%iNlw z2}Tns1*Z`lz9ktC%*;>RMw}u#B}f%D#;LA(;kI_%@Mr6>voXO)8BVPa8VQXR2&ml= zZo%MK*VGq5qybSVNbyLTe?b)rsBb5#+4`S0ASbMdO(j!uE6?MB6L3Q+Xc25NS!*Fa zrL}gfGp!tz+QQ6J*p2#uni#~%v6Y{Ijocoh@V&RvPLCB<7OHrC7fu7UGnj1xTYg5t zA&zk%mWwa=3}(Z3(1Ofo3doGwqjEx)7IOl3o#D5^g?Xe=fWj*#O$~3zq@Xrnb6AKi znPy80tRH4?rKD0&gJxNEjGdkRBFL;j1%u>=);|tzg6)^Zp5z4VRwkzwkFP+nTuRmO zazNNkz*(Cqvf3_;dDn)kiyfO0%u1iL3olA;9xaT2SJ+m_?cv;b1+kT3b(djUbg1=@ z#74UvHK(NH<`M^y>0&pF2+g?#83k1hidrlqP*HFh=}4sql1>1dWCWt3(KItj3wT42 zf?AEkZi-?%9bPgTjF~i?F~F(7EFFhl`L6Xb#At9mg5{L%Gz#z#9hTZ|2&1oeza*IC@SU&(@rKOmX#(P+9KwuRPkD(IT+zfB7 z&rXq8FhIKnSgg))qR^99A#GFd;Ue3LURq70Ja?g;`LrCYG%$c;>EVd|g6i|S>~_aY z7>&ht3U6*X&Mrmx6l)D4AMn_MMN!u;1DeMI>Lu%y# zrc$g91%p1!-6NY}UsvcycDsd{N5yGVv!M{?R|^ZoX0Lc(VdE`0pFE`51R6&;T<~^b z2zkLOm}2B4ovI&u6t_OSx%kf{wyng#Qx*PS^zm`^2QhxKtPqAn!JA+xYB+Qh&=j^R zbAck3Pq*#alY0n|;5Kqo@tL8(@hv4IQLyET)v|<}ncPm#&)?>ZiZ&y&sKji<){ZS; zY`JS}X`b*2Lp;GH;j4c=C<<>zP^JACLCqTiLKM0<;o-wlNI$e9*u1a)N41xu`Xx6+Df4Jy zS!%+uhNCDxm`GW6SF%HtHbUgrtK#j#MW&nuLO$4mey0F{q z6tN+sIEm~dw9*Q2GiJV5J-)&S6>7|;;CAmqo^ac#lSC(KX=Xmf>PYd3A7}YVO*szK zfNc@Ao9xVB8y1rV_i-8B-b0`sYXCD=ZS))Jk1BIUZbnK$F4JjNYXibDqk(D#ukAvR za>0by2rkO)SdsAru%Z6YgWJ|RWwhW0wswc3gxF+ki@zA|huOBU8My$Cu-pv7UqP;l zg=fRzsadSH1z|u8Z$Xsx;3n!jn4DKx0ly_@zn5xZm`ceY>6B&2kj++p5Zrzvl8fcl za#N>w%(F1BsZqo>GEQ#PPspZ2L%pclKn>XHy8d_>)Sy>;eDhwFY_SBIGh!ZPaEO} zmIjBH3~;u*@{^8jM#Eps42m3zy8iz1>W#IhGYcCS-B5Z^k`g{^TM=$@%@nTN8z1k^ zMlTXBTj{!ss!fHd6ueahDFqGqj(L18BDWv|r0{!2DUk`YJw&$;sGxmZJ1yhT>SL7xo z2x~T3ck_=NufGLu!1l9bnR|GSRdXqxVfFEm%XjZGN{LOfsky-1KuYMJCr@Q+=z>CFZ$`BRv=@dpc zpDz>=Y=PM+pK$Xc@;nnBhEsYflXi7O(QrKc48m-&x#l>4=Til7TCpDF8 z&IzEkg)RwkiQ&zMwKt4ycYJ{$umyZThecoQH~eA=Wylhb5q{rS%=@h?KhBQN8CcQG z@TJvmYvv3UcfFj9aU(d8a{yeQtJ&^)^5!+-bWtSS{Qcv^4erxe7QO&*cwyIKKii!E<|h) z*Z>^gQQV$6w2#=#5{ksOXPYu~ox4rsW=8!{SC_7Wbde>;4s1lGu)%B^+wK#gP$??N z)Hp+Y9F|ZOHlq(i8}J}TkxIi@AomfQI1GUsR^--0Ay5-+JYGIgn<=+_;g!b`PPkE^ zMsA2y{ea*d;H=GhQ_x{|a6jfLpuJjNe*Sd9f9*KbHVwA{8+uLVsP1fRB>)>lYD^4T ztpy0nqrlbxY)klRsBc{!Da5x z-k&`r+_v(<5EDah6dRtRCd^djD_dL5T(h)IU}j2e=o|pIrIC7hw%X__o-gndZWfU^ z9mvLpeB~XD4TfW&Dr!sy4RqU?D#@Hxms=IHeK83!^)Bqx*!KIgXX_+L=NF&RX^&JF zLzJ65GLqPIjOKZ~CfRySRr|Jh?KdQGhZL6L$~dq^0>8rMQy)THJm1a@VO736Sz23i zE-1h8OQi)?zP4(`?I805y{5TA<(Oev`tZl_T(CV~US@{_uz}mx57eqVvI0uC+RD<@ zQjJZ=DY3z00&ic#nbyl@!Paou44V;T36+0ck4FnZ8N^$ddW@2jzr?YWX z#AX95rqf#!w{PJaL)BJHZd5gy@rzr5P+oCuNnc=0V}q90o(_JjZ@9P(X3!k%=B%$T zD0QV0#3!XL5h&RQ}timRRT7W+sY02o)0NEnRhbNsPfHQ!&;OZv0Wjy1)g|WRC}YEnsAF@ zGo{skR+^dZ@sp7!3pk&&wiTWe+%Ts4qcg=n!ty1FLk5v0JT$_+KVX%XA1 zp}OK=URq{tZC<5kc)v6$!etoPvN^JqPov>OQwx9@ULmFfG9xW*H8~B=d4&J*WB(Mv z#pJzoX~gUGY}0+00|7ZW(%XBl7yIkFGMZ^tI+G(Ev~sNrb-c5pk9ZN;a?hEYwhaiJgBp1*jep^;w9Z7&VY z6&1jii3H{u#0U;@37jXeS21${&qfTX9cD1u6l^F#FpdR7f{L#wH{Y!9@#DvMnx1zj zI6~vE7G_3pLfF;kx_{q=^6Y+})ph}S^<;UBo7H*}6M9cau>(n)nUu3D?!)3zZ?Ct$ ztdgT!C#n6cxjpEtEbSsTQ4||r%58_bFgpb+5>qzYr``yVG^0+#SDnUoR_=oWHXu^1 zS(FY|P+K+FG5MC=lM~*iI7BBZ24LHdtkApq^5rjYHbX*fN@0sgDh&uUVHOi|>Fi^O zj`M*Q2EiycG(Sx`G3et2jeT&$Gje}~vRivuj$EydCRFL=vJdu^JN}7wOaB_N&8Srn z9OTC$5wU&NM{1hew7o1FQOY7@r{A>O^0{}a5NTR`R{}L+i*lpc%xAA$Ioovwmtun& zxxEA|QUg6r?wMe-<615#wcpzWld+kHRS2GG{|l&XZn!!H8{9@}o^za51;UY7bZW^S zgFnO|ef)&U!s{CJ_yfXCf^<;H1C5LfQ+?KEDdT!$erv7?UR3QF8QD8b#aAl zn9FOx_A$a#*$v!A?fVl>-&L9=x4W7ev2i~E*ocXHe@cp&hz=t+14o zZ0HFGHd50*WB&)RAxQ1?8w@w>$(B8CxtBKE4QMZ4M`r5xpI=v1SttVamtG^1pl=5@ zR3dx}VwL=Ff1Ca8@nhuF!D2o+$r}=GF?qIW&~ER&NA(gLWd*n9W^UehJox9D|LHuj z&ELmmbl^5vR#^#bQ0KJ5MsD`hgr>VOZO3*7jgt_0Y3&@{u<0P^G&P1O(bet1_CjHk znF5GWFNqDZmS?9P_jpRBV;zpm?peh{hF?V%IxEXQ9F`JIW4m8kKQ;wvY?_%+lfC=d z0K#;*VtBeneP0C$K!z8Cw3(_-t@QL`Tw{nDo2T(NUu-<><4pN|Pk;g$Vhi^d4-?xgej~{6 zqCm~l)YLSLCCnd#7~R@I&4ib!WH7M9ZJMH_iNS7QlPQ<2n%MX-8~m$~8yZ9!AG}$9 zuDIdO>WRq-bp)JlJ2kyBw+fBB;Wo4dei6k+ftSM8dX#$$v{ek%Z?W+M#C%W%Y=G8v z^ZVamd#0zQj8MU5XH;Y+)O)UF7cxYP-Gugh?av?LIL?P+{1#g-KUC3dd=c9u zu+1+hYz$L!(*=mcDX~G7xYwZG)6edJimOjvvX8)3&06HWvi+q#pXNqRObDVNk_5;F zi7hY^l1`-lw$8R|>=}972($5S44$VST=)414){j~JFBZZ2T9H|JT}&1skV_CL~aLh zeVT+RH)3!Hul@DL)8$1>a@cq@_(^>|u~iB-_ZBwfgf=9KF`-6mq6UTSs9+PxP*#QQ z`<>Kuxdy2S#d||n*Ol9RyhIcmsL^AR4Mc*?G1y6))q94^_8&YsEy?0~8@6tfjhuJNY5HB-YSa&a+lH?Fa?V6V_ea-No) za$5$y-Vr=+4iJG&gS*wP<5aj6m)BNXH#V3`zn`Gm-q+7C6}!0zO&7cl9PK-Qck1rl zt}Se{U4(Y`44c9a9X%S|q>phE;~^w9IlPk^)J3y-{=y)QxlKIw5~hRZo{-zBp$`RA ze+7C8qdL)*j6_lG9mGNrEY~i`_;zc^3TAqE z!||fFHtQ0WcOk}T&-?EK+eekcjX8l61;x!-Us+#&9x>Y0rQHT>9N`*>tby2~A{qU$ zix&yr#7|WesexMqsrlJs?IdfeqJ_z7K3$cKgh=4cqa` z&rZQ^QEZnR9gXT;X8Li z;X7IvaZucD$5C*Hdm|!;L58_GMrMFU;nMXHudBD+YP+X}p#e{B8Zhai|j%#e>c5a$=8@bVK$4@n#g2!kw!Pap3?ks-huAio+JjVmf}O1poj507*qoM6N<$ Ef@c&sNB{r; literal 0 HcmV?d00001