content: /*
   * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
   *
   * 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.activiti.engine.impl.cfg.multitenant;
  
  import java.util.concurrent.ExecutorService;
  import javax.sql.DataSource;
  import org.activiti.api.runtime.shared.identity.UserGroupManager;
  import org.activiti.engine.ProcessEngine;
  import org.activiti.engine.ProcessEngineConfiguration;
  import org.activiti.engine.impl.asyncexecutor.AsyncExecutor;
  import org.activiti.engine.impl.asyncexecutor.multitenant.ExecutorPerTenantAsyncExecutor;
  import org.activiti.engine.impl.asyncexecutor.multitenant.SharedExecutorServiceAsyncExecutor;
  import org.activiti.engine.impl.asyncexecutor.multitenant.TenantAwareAsyncExecutor;
  import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
  import org.activiti.engine.impl.db.DbIdGenerator;
  import org.activiti.engine.impl.interceptor.CommandInterceptor;
  import org.activiti.engine.impl.persistence.StrongUuidGenerator;
  import org.activiti.engine.repository.DeploymentBuilder;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * A {@link ProcessEngineConfiguration} that builds a multi tenant {@link ProcessEngine} where
   * each tenant has its own database schema.
   *
   * If multitenancy is needed and no data isolation is needed: the default {@link ProcessEngineConfigurationImpl}
   * of Activiti is multitenant enabled out of the box by setting a tenant identifier on a {@link DeploymentBuilder}.
   *
   * This configuration has following characteristics:
   *
   * - It needs a {@link TenantInfoHolder} to determine which tenant is currently 'active'. Ie for which
   *   tenant a certain API call is executed.
   *
   * - The {@link StrongUuidGenerator} is used by default. The 'regular' {@link DbIdGenerator} cannot be used with this config.
   *
   * - Adding tenants (also after boot!) is done using the {@link #registerTenant(String, DataSource)} operations.
   *
   * - Currently, this config does not work with the 'old' {@link JobExecutor}, but only with the newer {@link AsyncExecutor}.
   *   There are two different implementations:
   *     - The {@link ExecutorPerTenantAsyncExecutor}: creates one full {@link AsyncExecutor} for each tenant.
   *     - The {@link SharedExecutorServiceAsyncExecutor}: created acquisition threads for each tenant, but the
   *       job execution is done using a process engine shared {@link ExecutorService}.
   *   The {@link AsyncExecutor} needs to be injected using the {@link #setAsyncExecutor(AsyncExecutor)} method on this class.
   *
   * databasetype
   *
   *  @deprecated multi-tenant code will be removed in future version of Activiti and Activiti Cloud
   */
  @Deprecated
  public class MultiSchemaMultiTenantProcessEngineConfiguration extends ProcessEngineConfigurationImpl {
  
      private static final Logger logger = LoggerFactory.getLogger(
          MultiSchemaMultiTenantProcessEngineConfiguration.class
      );
  
      protected TenantInfoHolder tenantInfoHolder;
      protected boolean booted;
  
      public MultiSchemaMultiTenantProcessEngineConfiguration(TenantInfoHolder tenantInfoHolder) {
          this.tenantInfoHolder = tenantInfoHolder;
  
          // Using the UUID generator, as otherwise the ids are pulled from a global pool of ids, backed by
          // a database table. Which is impossible with a mult-database-schema setup.
  
          // Also: it avoids the need for having a process definition cache for each tenant
  
          this.idGenerator = new StrongUuidGenerator();
  
          this.dataSource = new TenantAwareDataSource(tenantInfoHolder);
      }
  
      /**
       * Add a new {@link DataSource} for a tenant, identified by the provided tenantId, to the engine.
       * This can be done after the engine has booted up.
       *
       * Note that the tenant identifier must have been added to the {@link TenantInfoHolder} *prior*
       * to calling this method.
       */
      public void registerTenant(String tenantId, DataSource dataSource) {
          ((TenantAwareDataSource) super.getDataSource()).addDataSource(tenantId, dataSource);
  
          if (booted) {
              createTenantSchema(tenantId);
  
              createTenantAsyncJobExecutor(tenantId);
  
              tenantInfoHolder.setCurrentTenantId(tenantId);
              super.postProcessEngineInitialisation();
              tenantInfoHolder.clearCurrentTenantId();
          }
      }
  
      @Override
      public void initAsyncExecutor() {
          if (asyncExecutor == null) {
              asyncExecutor = new ExecutorPerTenantAsyncExecutor(tenantInfoHolder);
          }
  
          super.initAsyncExecutor();
  
          if (asyncExecutor instanceof TenantAwareAsyncExecutor) {
              for (String tenantId : tenantInfoHolder.getAllTenants()) {
                  ((TenantAwareAsyncExecutor) asyncExecutor).addTenantAsyncExecutor(tenantId, false); // false -> will be started later with all the other executors
              }
          }
      }
  
      @Override
      public ProcessEngine buildProcessEngine() {
          // Disable schema creation/validation by setting it to null.
          // We'll do it manually, see buildProcessEngine() method (hence why it's copied first)
          String originalDatabaseSchemaUpdate = this.databaseSchemaUpdate;
          this.databaseSchemaUpdate = null;
  
          // Also, we shouldn't start the async executor until *after* the schema's have been created
          boolean originalIsAutoActivateAsyncExecutor = this.asyncExecutorActivate;
          this.asyncExecutorActivate = false;
  
          ProcessEngine processEngine = super.buildProcessEngine();
  
          // Reset to original values
          this.databaseSchemaUpdate = originalDatabaseSchemaUpdate;
          this.asyncExecutorActivate = originalIsAutoActivateAsyncExecutor;
  
          // Create tenant schema
          for (String tenantId : tenantInfoHolder.getAllTenants()) {
              createTenantSchema(tenantId);
          }
  
          // Start async executor
          if (asyncExecutor != null && originalIsAutoActivateAsyncExecutor) {
              asyncExecutor.start();
          }
  
          booted = true;
          return processEngine;
      }
  
      protected void createTenantSchema(String tenantId) {
          logger.info("creating/validating database schema for tenant " + tenantId);
          tenantInfoHolder.setCurrentTenantId(tenantId);
          getCommandExecutor().execute(getSchemaCommandConfig(), new ExecuteSchemaOperationCommand(databaseSchemaUpdate));
          tenantInfoHolder.clearCurrentTenantId();
      }
  
      protected void createTenantAsyncJobExecutor(String tenantId) {
          ((TenantAwareAsyncExecutor) asyncExecutor).addTenantAsyncExecutor(
              tenantId,
              isAsyncExecutorActivate() && booted
          );
      }
  
      @Override
      public CommandInterceptor createTransactionInterceptor() {
          return null;
      }
  
      @Override
      protected void postProcessEngineInitialisation() {
          // empty here. will be done in registerTenant
      }
  
      @Override
      public UserGroupManager getUserGroupManager() {
          return null; //no external identity provider supplied
      }
  }
  