All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.activiti.engine.impl.cfg.multitenant.MultiSchemaMultiTenantProcessEngineConfiguration Maven / Gradle / Ivy

/* 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.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.UserGroupLookupProxy;
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
 * 

 */
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 UserGroupLookupProxy getUserGroupLookupProxy() {
    return null; //no external identity provider supplied
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy