blob: ea980e5164dd25bd2b3900d4a59d1fd08b72e6ff [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.fineract.infrastructure.core.service.database;
import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toJdbcUrl;
import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toProtocol;
import com.zaxxer.hikari.HikariConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
*
* Factory class to get data source service based on the details stored in {@link FineractPlatformTenantConnection}
* variable
*
*/
@Component
@Slf4j
public class DataSourcePerTenantServiceFactory {
private final HikariConfig hikariConfig;
private final FineractProperties fineractProperties;
private final ApplicationContext context;
private final DataSource tenantDataSource;
private final HikariDataSourceFactory hikariDataSourceFactory;
private final DatabasePasswordEncryptor databasePasswordEncryptor;
public DataSourcePerTenantServiceFactory(@Qualifier("hikariTenantDataSource") DataSource tenantDataSource, HikariConfig hikariConfig,
FineractProperties fineractProperties, ApplicationContext context, HikariDataSourceFactory hikariDataSourceFactory,
DatabasePasswordEncryptor databasePasswordEncryptor) {
this.hikariConfig = hikariConfig;
this.fineractProperties = fineractProperties;
this.context = context;
this.tenantDataSource = tenantDataSource;
this.hikariDataSourceFactory = hikariDataSourceFactory;
this.databasePasswordEncryptor = databasePasswordEncryptor;
}
@SuppressFBWarnings(value = "SLF4J_SIGN_ONLY_FORMAT")
public DataSource createNewDataSourceFor(final FineractPlatformTenantConnection tenantConnection) {
if (!databasePasswordEncryptor.isMasterPasswordHashValid(tenantConnection.getMasterPasswordHash())) {
throw new IllegalArgumentException(
"Invalid master password on tenant connection %d.".formatted(tenantConnection.getConnectionId()));
}
String protocol = toProtocol(tenantDataSource);
// Default properties for Writing
String schemaServer = tenantConnection.getSchemaServer();
String schemaPort = tenantConnection.getSchemaServerPort();
String schemaName = tenantConnection.getSchemaName();
String schemaUsername = tenantConnection.getSchemaUsername();
String schemaPassword = tenantConnection.getSchemaPassword();
String schemaConnectionParameters = tenantConnection.getSchemaConnectionParameters();
// Properties to ReadOnly case
if (fineractProperties.getMode().isReadOnlyMode()) {
schemaServer = StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaServer(), schemaServer);
schemaPort = StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaServerPort(), schemaPort);
schemaName = StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaName(), schemaName);
schemaUsername = StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaUsername(), schemaUsername);
schemaPassword = StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaPassword(), schemaPassword);
schemaConnectionParameters = StringUtils.defaultIfBlank(tenantConnection.getReadOnlySchemaConnectionParameters(),
schemaConnectionParameters);
}
String jdbcUrl = toJdbcUrl(protocol, schemaServer, schemaPort, schemaName, schemaConnectionParameters);
log.debug("{}", jdbcUrl);
HikariConfig config = new HikariConfig();
config.setReadOnly(fineractProperties.getMode().isReadOnlyMode());
config.setJdbcUrl(jdbcUrl);
config.setPoolName(schemaName + "_pool");
config.setUsername(schemaUsername);
config.setPassword(databasePasswordEncryptor.decrypt(schemaPassword));
config.setMinimumIdle(getMinPoolSize(tenantConnection));
config.setMaximumPoolSize(getMaxPoolSize(tenantConnection));
config.setValidationTimeout(tenantConnection.getValidationInterval());
config.setDriverClassName(hikariConfig.getDriverClassName());
config.setConnectionTestQuery(hikariConfig.getConnectionTestQuery());
config.setAutoCommit(hikariConfig.isAutoCommit());
// https://github.com/brettwooldridge/HikariCP/wiki/MBean-(JMX)-Monitoring-and-Management
config.setRegisterMbeans(true);
// https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
// These are the properties for each Tenant DB; the same configuration
// is also in src/main/resources/META-INF/spring/hikariDataSource.xml
// for the all Tenants DB -->
config.setDataSourceProperties(hikariConfig.getDataSourceProperties());
return hikariDataSourceFactory.create(config);
}
private int getMaxPoolSize(FineractPlatformTenantConnection tenantConnection) {
FineractProperties.FineractConfigProperties configOverride = fineractProperties.getTenant().getConfig();
if (configOverride.isMaxPoolSizeSet()) {
int maxPoolSize = configOverride.getMaxPoolSize();
log.info("Overriding tenant datasource maximum pool size configuration to {}", maxPoolSize);
return maxPoolSize;
} else {
return tenantConnection.getMaxActive();
}
}
private int getMinPoolSize(FineractPlatformTenantConnection tenantConnection) {
FineractProperties.FineractConfigProperties configOverride = fineractProperties.getTenant().getConfig();
if (configOverride.isMinPoolSizeSet()) {
int minPoolSize = configOverride.getMinPoolSize();
log.info("Overriding tenant datasource minimum pool size configuration to {}", minPoolSize);
return minPoolSize;
} else {
return tenantConnection.getInitialSize();
}
}
}