RED-7094 - extracted liquibase code
This commit is contained in:
parent
bd153595d0
commit
6b25b56e10
@ -3,6 +3,7 @@ package com.knecon.fforesight.databasetenantcommons;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
@ -13,7 +14,7 @@ import com.knecon.fforesight.tenantcommons.MultiTenancyAutoConfiguration;
|
||||
@AutoConfiguration
|
||||
@AutoConfigureAfter(MultiTenancyAutoConfiguration.class)
|
||||
@ImportAutoConfiguration(MultiTenancyAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(TenantHikariSettings.class)
|
||||
@EnableConfigurationProperties({TenantHikariSettings.class, LiquibaseProperties.class})
|
||||
public class DatabaseTenantCommonsAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public class TenantMessagingConfiguration {
|
||||
@Bean
|
||||
public Queue persistenceServiceTenantDLQ() {
|
||||
|
||||
return QueueBuilder.durable(tenantCreatedEventQueue).build();
|
||||
return QueueBuilder.durable(tenantCreatedDLQ).build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,71 +1,31 @@
|
||||
package com.knecon.fforesight.databasetenantcommons.providers;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.postgresql.util.PSQLException;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.StatementCallback;
|
||||
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantCreatedEvent;
|
||||
import com.knecon.fforesight.databasetenantcommons.providers.utils.JDBCUtils;
|
||||
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
|
||||
import com.knecon.fforesight.tenantcommons.TenantProvider;
|
||||
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
|
||||
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import liquibase.integration.spring.SpringLiquibase;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@EnableConfigurationProperties(LiquibaseProperties.class)
|
||||
@RequiredArgsConstructor
|
||||
public class TenantCreatedListener {
|
||||
|
||||
private static final Set<String> SUPPORTED_DATABASES = Set.of("postgresql");
|
||||
private static final Set<String> SQL_CONNECTION_ERROR_CODES = Set.of(
|
||||
// connection_exception
|
||||
"08000",
|
||||
// connection_does_not_exist
|
||||
"08003",
|
||||
// connection_failure
|
||||
"08006",
|
||||
// invalid_catalog_name
|
||||
"3D000");
|
||||
private final TenantLiquibaseInitializer tenantLiquibaseInitializer;
|
||||
|
||||
private final LiquibaseProperties liquibaseProperties;
|
||||
private final ResourceLoader resourceLoader;
|
||||
private final MigrationService migrationService;
|
||||
private final TenantProvider tenantProvider;
|
||||
private final EncryptionDecryptionService encryptionDecryptionService;
|
||||
@Value("${fforesight.multitenancy.tenant-created-queue:tenant-created}")
|
||||
private String tenantCreatedQueue;
|
||||
|
||||
|
||||
public TenantCreatedListener(@Qualifier("tenantLiquibaseProperties") LiquibaseProperties liquibaseProperties,
|
||||
ResourceLoader resourceLoader,
|
||||
EncryptionDecryptionService encryptionDecryptionService,
|
||||
@Autowired(required = false) MigrationService migrationService,
|
||||
TenantProvider tenantProvider) {
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
|
||||
this.liquibaseProperties = liquibaseProperties;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.encryptionDecryptionService = encryptionDecryptionService;
|
||||
this.migrationService = migrationService;
|
||||
this.tenantProvider = tenantProvider;
|
||||
log.info("Listener for tenant-created started for queue: {}", tenantCreatedQueue);
|
||||
}
|
||||
|
||||
|
||||
@ -73,99 +33,7 @@ public class TenantCreatedListener {
|
||||
@RabbitListener(queues = "${fforesight.multitenancy.tenant-created-queue:tenant-created}")
|
||||
public void createTenant(TenantCreatedEvent tenantRequest) {
|
||||
|
||||
var tenant = tenantProvider.getTenant(tenantRequest.getTenantId());
|
||||
|
||||
createSchema(tenant);
|
||||
|
||||
var jdbcUrl = JDBCUtils.buildJdbcUrlWithSchema(tenant.getDatabaseConnection());
|
||||
validateJdbcUrl(jdbcUrl);
|
||||
|
||||
try (Connection connection = DriverManager.getConnection(jdbcUrl,
|
||||
tenant.getDatabaseConnection().getUsername(),
|
||||
encryptionDecryptionService.decrypt(tenant.getDatabaseConnection().getPassword()))) {
|
||||
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
|
||||
runLiquibase(tenantDataSource);
|
||||
} catch (PSQLException e) {
|
||||
handleClientException(e);
|
||||
handleInternalException(e);
|
||||
}
|
||||
|
||||
if (migrationService != null) {
|
||||
migrationService.runForTenant(tenantRequest.getTenantId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void createSchema(TenantResponse tenantRequest) {
|
||||
|
||||
var jdbcUrl = JDBCUtils.buildJdbcUrl(tenantRequest.getDatabaseConnection());
|
||||
try (Connection connection = DriverManager.getConnection(jdbcUrl,
|
||||
tenantRequest.getDatabaseConnection().getUsername(),
|
||||
encryptionDecryptionService.decrypt(tenantRequest.getDatabaseConnection().getPassword()))) {
|
||||
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(tenantDataSource);
|
||||
jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("CREATE SCHEMA " + tenantRequest.getDatabaseConnection().getSchema()));
|
||||
jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("GRANT USAGE ON SCHEMA " + tenantRequest.getDatabaseConnection()
|
||||
.getSchema() + " TO " + tenantRequest.getDatabaseConnection().getUsername()));
|
||||
} catch (Exception e) {
|
||||
log.info("Could not create schema, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
private void validateJdbcUrl(String jdbcUrl) {
|
||||
|
||||
try {
|
||||
// just create a URI object to check if the string is a valid URI
|
||||
var uri = new URI(jdbcUrl);
|
||||
var subUri = new URI(uri.getSchemeSpecificPart());
|
||||
|
||||
if (uri.getScheme() == null || subUri.getScheme() == null || !uri.getScheme().equals("jdbc") || !SUPPORTED_DATABASES.contains(subUri.getScheme())) {
|
||||
throw new IllegalArgumentException("Your jdbcUrl is not valid.");
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Your jdbcUrl is not valid.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void runLiquibase(DataSource dataSource) throws LiquibaseException {
|
||||
|
||||
SpringLiquibase liquibase = getSpringLiquibase(dataSource);
|
||||
liquibase.afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
private void handleClientException(PSQLException e) {
|
||||
|
||||
if (e.getSQLState().equals("28000") || e.getSQLState().equals("28P01")) {
|
||||
throw new IllegalArgumentException("Database credentials are not correct. Please check them.");
|
||||
}
|
||||
if (SQL_CONNECTION_ERROR_CODES.contains(e.getSQLState())) {
|
||||
throw new IllegalArgumentException("Error when connecting to tenant database. Please check the jdbcUrl parameter.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleInternalException(PSQLException e) {
|
||||
|
||||
log.error(String.format("Connection to tenant DB failed with SQL state %s. Please check if the tenant DB is still running. " + //
|
||||
"If yes please check the connection configuration.", e.getSQLState()), e);
|
||||
throw new RuntimeException("Could not connect to the tenant DB. This is an internal error.", e);
|
||||
}
|
||||
|
||||
|
||||
protected SpringLiquibase getSpringLiquibase(DataSource dataSource) {
|
||||
|
||||
SpringLiquibase liquibase = new SpringLiquibase();
|
||||
liquibase.setResourceLoader(resourceLoader);
|
||||
liquibase.setDataSource(dataSource);
|
||||
liquibase.setChangeLog(liquibaseProperties.getChangeLog());
|
||||
liquibase.setContexts(liquibaseProperties.getContexts());
|
||||
return liquibase;
|
||||
tenantLiquibaseInitializer.initializeTenant(tenantRequest.getTenantId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,167 @@
|
||||
package com.knecon.fforesight.databasetenantcommons.providers;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.postgresql.util.PSQLException;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.StatementCallback;
|
||||
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantCreatedEvent;
|
||||
import com.knecon.fforesight.databasetenantcommons.providers.utils.JDBCUtils;
|
||||
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
|
||||
import com.knecon.fforesight.tenantcommons.TenantProvider;
|
||||
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
|
||||
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import liquibase.integration.spring.SpringLiquibase;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TenantLiquibaseInitializer {
|
||||
|
||||
private static final Set<String> SUPPORTED_DATABASES = Set.of("postgresql");
|
||||
private static final Set<String> SQL_CONNECTION_ERROR_CODES = Set.of(
|
||||
// connection_exception
|
||||
"08000",
|
||||
// connection_does_not_exist
|
||||
"08003",
|
||||
// connection_failure
|
||||
"08006",
|
||||
// invalid_catalog_name
|
||||
"3D000");
|
||||
|
||||
private final LiquibaseProperties liquibaseProperties;
|
||||
private final ResourceLoader resourceLoader;
|
||||
private final MigrationService migrationService;
|
||||
private final TenantProvider tenantProvider;
|
||||
private final EncryptionDecryptionService encryptionDecryptionService;
|
||||
|
||||
|
||||
public TenantLiquibaseInitializer(@Qualifier("tenantLiquibaseProperties") LiquibaseProperties liquibaseProperties,
|
||||
ResourceLoader resourceLoader,
|
||||
EncryptionDecryptionService encryptionDecryptionService,
|
||||
@Autowired(required = false) MigrationService migrationService,
|
||||
TenantProvider tenantProvider) {
|
||||
|
||||
this.liquibaseProperties = liquibaseProperties;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.encryptionDecryptionService = encryptionDecryptionService;
|
||||
this.migrationService = migrationService;
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public void initializeTenant(String tenantId) {
|
||||
|
||||
var tenant = tenantProvider.getTenant(tenantId);
|
||||
|
||||
createSchema(tenant);
|
||||
|
||||
var jdbcUrl = JDBCUtils.buildJdbcUrlWithSchema(tenant.getDatabaseConnection());
|
||||
validateJdbcUrl(jdbcUrl);
|
||||
|
||||
try (Connection connection = DriverManager.getConnection(jdbcUrl,
|
||||
tenant.getDatabaseConnection().getUsername(),
|
||||
encryptionDecryptionService.decrypt(tenant.getDatabaseConnection().getPassword()))) {
|
||||
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
|
||||
runLiquibase(tenantDataSource);
|
||||
} catch (PSQLException e) {
|
||||
handleClientException(e);
|
||||
handleInternalException(e);
|
||||
}
|
||||
|
||||
if (migrationService != null) {
|
||||
migrationService.runForTenant(tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void createSchema(TenantResponse tenantRequest) {
|
||||
|
||||
var jdbcUrl = JDBCUtils.buildJdbcUrl(tenantRequest.getDatabaseConnection());
|
||||
try (Connection connection = DriverManager.getConnection(jdbcUrl,
|
||||
tenantRequest.getDatabaseConnection().getUsername(),
|
||||
encryptionDecryptionService.decrypt(tenantRequest.getDatabaseConnection().getPassword()))) {
|
||||
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(tenantDataSource);
|
||||
jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("CREATE SCHEMA " + tenantRequest.getDatabaseConnection().getSchema()));
|
||||
jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("GRANT USAGE ON SCHEMA " + tenantRequest.getDatabaseConnection()
|
||||
.getSchema() + " TO " + tenantRequest.getDatabaseConnection().getUsername()));
|
||||
} catch (Exception e) {
|
||||
log.info("Could not create schema, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
private void validateJdbcUrl(String jdbcUrl) {
|
||||
|
||||
try {
|
||||
// just create a URI object to check if the string is a valid URI
|
||||
var uri = new URI(jdbcUrl);
|
||||
var subUri = new URI(uri.getSchemeSpecificPart());
|
||||
|
||||
if (uri.getScheme() == null || subUri.getScheme() == null || !uri.getScheme().equals("jdbc") || !SUPPORTED_DATABASES.contains(subUri.getScheme())) {
|
||||
throw new IllegalArgumentException("Your jdbcUrl is not valid.");
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Your jdbcUrl is not valid.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void runLiquibase(DataSource dataSource) throws LiquibaseException {
|
||||
|
||||
SpringLiquibase liquibase = getSpringLiquibase(dataSource);
|
||||
liquibase.afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
private void handleClientException(PSQLException e) {
|
||||
|
||||
if (e.getSQLState().equals("28000") || e.getSQLState().equals("28P01")) {
|
||||
throw new IllegalArgumentException("Database credentials are not correct. Please check them.");
|
||||
}
|
||||
if (SQL_CONNECTION_ERROR_CODES.contains(e.getSQLState())) {
|
||||
throw new IllegalArgumentException("Error when connecting to tenant database. Please check the jdbcUrl parameter.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleInternalException(PSQLException e) {
|
||||
|
||||
log.error(String.format("Connection to tenant DB failed with SQL state %s. Please check if the tenant DB is still running. " + //
|
||||
"If yes please check the connection configuration.", e.getSQLState()), e);
|
||||
throw new RuntimeException("Could not connect to the tenant DB. This is an internal error.", e);
|
||||
}
|
||||
|
||||
|
||||
protected SpringLiquibase getSpringLiquibase(DataSource dataSource) {
|
||||
|
||||
SpringLiquibase liquibase = new SpringLiquibase();
|
||||
liquibase.setResourceLoader(resourceLoader);
|
||||
liquibase.setDataSource(dataSource);
|
||||
liquibase.setChangeLog(liquibaseProperties.getChangeLog());
|
||||
liquibase.setContexts(liquibaseProperties.getContexts());
|
||||
return liquibase;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user