RED-4512: Implemented multitenancy for persistence

This commit is contained in:
deiflaender 2022-07-01 09:19:15 +02:00
parent a4781dba7d
commit 0adf8026ce
93 changed files with 1428 additions and 404 deletions

View File

@ -0,0 +1,19 @@
package com.iqser.red.service.persistence.service.v1.api.model.multitenancy;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Tenant {
private String tenantId;
private String jdbcUrl;
private String user;
private String password;
}

View File

@ -0,0 +1,17 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.multitenancy.Tenant;
public interface TenantsResource {
@ResponseStatus(value = HttpStatus.OK)
@PostMapping("/tenants")
void createTenant(@RequestBody Tenant tenant);
}

View File

@ -1,29 +1,16 @@
package com.iqser.red.service.persistence.management.v1.processor;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.support.PageJacksonModule;
import org.springframework.cloud.openfeign.support.SortJacksonModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import com.iqser.red.service.persistence.management.v1.processor.client.PDFTronRedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.CommentEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.audit.AuditEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ColorsEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.download.DownloadStatusEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.index.IndexInformationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.migration.MigrationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.notification.NotificationEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ColorsRepository;
@Configuration
@ComponentScan
@EntityScan(basePackageClasses = {CommentEntity.class, AuditEntity.class, NotificationEntity.class, ColorsEntity.class, DossierEntity.class, DownloadStatusEntity.class, MigrationEntity.class, IndexInformationEntity.class})
@EnableJpaRepositories(basePackageClasses = ColorsRepository.class)
@EnableFeignClients(basePackageClasses = {PDFTronRedactionClient.class})
public class PersistenceServiceProcessorConfiguration {

View File

@ -0,0 +1,29 @@
package com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "tenant")
public class TenantEntity {
@Id
private String tenantId;
@Column
private String jdbcUrl;
@Column(name = "username")
private String user;
@Column
private String password;
}

View File

@ -0,0 +1,26 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.stereotype.Component;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
@Component("currentTenantIdentifierResolver")
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getTenantId();
if (tenantId != null && !tenantId.isEmpty()) {
return tenantId;
} else {
// Allow bootstrapping the EntityManagerFactory, in which case no tenant is needed
return "BOOTSTRAP";
}
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}

View File

@ -0,0 +1,105 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.hibernate.engine.jdbc.connections.spi.AbstractDataSourceBasedMultiTenantConnectionProviderImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.stereotype.Component;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.TenantEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.EncryptionDecryptionService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.zaxxer.hikari.HikariDataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicDataSourceBasedMultiTenantConnectionProvider extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final String TENANT_POOL_NAME_SUFFIX = "DataSource";
private final DataSource masterDataSource;
private final DataSourceProperties masterDataSourceProperties;
private final TenantRepository masterTenantRepository;
private final EncryptionDecryptionService encryptionService;
@Value("${multitenancy.datasource-cache.maximumSize:100}")
private Long maximumSize;
@Value("${multitenancy.datasource-cache.expireAfterAccess:10}")
private Integer expireAfterAccess;
private LoadingCache<String, DataSource> tenantDataSources;
@PostConstruct
protected void createCache() {
tenantDataSources = CacheBuilder.newBuilder()
.maximumSize(maximumSize)
.expireAfterAccess(expireAfterAccess, TimeUnit.MINUTES)
.removalListener((RemovalListener<String, DataSource>) removal -> {
HikariDataSource ds = (HikariDataSource) removal.getValue();
ds.close(); // tear down properly
log.info("Closed datasource: {}", ds.getPoolName());
})
.build(new CacheLoader<String, DataSource>() {
public DataSource load(String key) {
TenantEntity tenant = masterTenantRepository.findByTenantId(key)
.orElseThrow(() -> new RuntimeException("No such tenant: " + key));
return createAndConfigureDataSource(tenant);
}
});
}
@Override
protected DataSource selectAnyDataSource() {
return masterDataSource;
}
@Override
public DataSource selectDataSource(String tenantIdentifier) {
try {
return tenantDataSources.get(tenantIdentifier);
} catch (ExecutionException e) {
throw new RuntimeException("Failed to load DataSource for tenant: " + tenantIdentifier);
}
}
private DataSource createAndConfigureDataSource(TenantEntity tenant) {
String decryptedPassword = encryptionService.decrypt(tenant.getPassword());
HikariDataSource ds = masterDataSourceProperties.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
ds.setUsername(tenant.getUser());
ds.setPassword(decryptedPassword);
ds.setJdbcUrl(tenant.getJdbcUrl());
ds.setPoolName(tenant.getTenantId() + TENANT_POOL_NAME_SUFFIX);
log.info("Configured datasource: {}", ds.getPoolName());
return ds;
}
}

View File

@ -0,0 +1,15 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.TenantEntity;
public interface TenantRepository extends JpaRepository<TenantEntity, String> {
@Query("select t from TenantEntity t where t.tenantId = :tenantId")
Optional<TenantEntity> findByTenantId(@Param("tenantId") String tenantId);
}

View File

@ -1,17 +1,7 @@
package com.iqser.red.service.persistence.management.v1.processor.utils.jdbc;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import static org.apache.commons.lang3.StringUtils.capitalize;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
@ -21,7 +11,22 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.capitalize;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.DynamicDataSourceBasedMultiTenantConnectionProvider;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@Service
@RequiredArgsConstructor
@ -35,7 +40,12 @@ public class JDBCWriteUtils {
private final EntityManager entityManager;
private final DynamicDataSourceBasedMultiTenantConnectionProvider dynamicDataSourceBasedMultiTenantConnectionProvider;
public <T> void saveBatch(final List<T> entities) {
jdbcTemplate.setDataSource(dynamicDataSourceBasedMultiTenantConnectionProvider.selectDataSource(TenantContext.getTenantId()));
if (entities.isEmpty()) {
return;
}

View File

@ -0,0 +1,29 @@
package com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class TenantContext {
private static InheritableThreadLocal<String> currentTenant = new InheritableThreadLocal<>();
public static void setTenantId(String tenantId) {
log.debug("Setting tenantId to " + tenantId);
currentTenant.set(tenantId);
}
public static String getTenantId() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}

View File

@ -1,15 +1,11 @@
package com.iqser.red.service.peristence.v1.server;
import com.iqser.red.commons.spring.DefaultWebMvcConfiguration;
import com.iqser.red.service.peristence.v1.server.client.RedactionClient;
import com.iqser.red.service.peristence.v1.server.configuration.CleanupDownloadSchedulerConfiguration;
import com.iqser.red.service.peristence.v1.server.configuration.MessagingConfiguration;
import com.iqser.red.service.peristence.v1.server.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.management.v1.processor.PersistenceServiceProcessorConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients;
@ -22,12 +18,20 @@ import org.springframework.retry.support.RetryTemplate;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import com.iqser.red.service.peristence.v1.server.client.RedactionClient;
import com.iqser.red.service.peristence.v1.server.configuration.CleanupDownloadSchedulerConfiguration;
import com.iqser.red.service.peristence.v1.server.configuration.MessagingConfiguration;
import com.iqser.red.service.peristence.v1.server.multitenancy.AsyncConfig;
import com.iqser.red.service.peristence.v1.server.multitenancy.MultiTenancyWebConfiguration;
import com.iqser.red.service.peristence.v1.server.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.management.v1.processor.PersistenceServiceProcessorConfiguration;
@EnableAsync
@EnableRetry
@EnableScheduling
@EnableConfigurationProperties(FileManagementServiceSettings.class)
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, CassandraAutoConfiguration.class})
@Import({DefaultWebMvcConfiguration.class, PersistenceServiceProcessorConfiguration.class, MessagingConfiguration.class, CleanupDownloadSchedulerConfiguration.class})
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, CassandraAutoConfiguration.class, DataSourceAutoConfiguration.class, LiquibaseAutoConfiguration.class})
@Import({MultiTenancyWebConfiguration.class, PersistenceServiceProcessorConfiguration.class, MessagingConfiguration.class, CleanupDownloadSchedulerConfiguration.class, AsyncConfig.class})
@EnableFeignClients(basePackageClasses = {RedactionClient.class})
public class Application {

View File

@ -0,0 +1,26 @@
package com.iqser.red.service.peristence.v1.server.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.peristence.v1.server.service.TenantManagementService;
import com.iqser.red.service.persistence.service.v1.api.model.multitenancy.Tenant;
import com.iqser.red.service.persistence.service.v1.api.resources.TenantsResource;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class TenantsController implements TenantsResource {
private final TenantManagementService tenantManagementService;
@PostMapping("/tenants")
public void createTenant(@RequestBody Tenant tenant) {
tenantManagementService.createTenant(tenant);
}
}

View File

@ -1,14 +1,14 @@
package com.iqser.red.service.peristence.v1.server.migration;
import java.util.List;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RestController

View File

@ -11,6 +11,8 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.MigrationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -24,26 +26,39 @@ public class MigrationStarterService {
private final FileManagementServiceSettings settings;
private final ApplicationContext ctx;
private final MigrationPersistenceService migrationPersistenceService;
private final TenantRepository tenantRepository;
@EventListener(ApplicationReadyEvent.class)
public void migrate() {
//This is always running on startup
seedMigration();
tenantRepository.findAll().forEach(tenant -> {
TenantContext.setTenantId(tenant.getTenantId());
//This is always running on startup
seedMigration();
});
// This should only run in post upgrade hook
if (settings.isMigrateOnly()) {
log.info("Start migration");
migrations.sort(Comparator.comparing(Migration::getVersion));
tenantRepository.findAll().forEach(tenant -> {
for (var migration : migrations) {
migration.run();
}
TenantContext.setTenantId(tenant.getTenantId());
log.info("Start migration for tentant: " + tenant);
migrations.sort(Comparator.comparing(Migration::getVersion));
for (var migration : migrations) {
migration.run();
}
});
log.info("Migration is finished");
System.exit(SpringApplication.exit(ctx, () -> 0));
}
}

View File

@ -1,18 +1,20 @@
package com.iqser.red.service.peristence.v1.server.migration.migrations;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.migration.Migration;
import com.iqser.red.service.peristence.v1.server.service.ManualRedactionProviderService;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.IdRemovalEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.AddRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RemoveRedactionPersistenceService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Setter

View File

@ -1,5 +1,10 @@
package com.iqser.red.service.peristence.v1.server.migration.migrations;
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.peristence.v1.server.migration.Migration;
@ -9,12 +14,9 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Slf4j
@Setter

View File

@ -1,18 +1,20 @@
package com.iqser.red.service.peristence.v1.server.migration.migrations;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.migration.Migration;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.BaseDictionaryEntry;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.TypeEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.EntryPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntryType;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
@Slf4j
@Setter

View File

@ -1,11 +1,13 @@
package com.iqser.red.service.peristence.v1.server.migration.migrations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.migration.Migration;
import com.iqser.red.service.peristence.v1.server.service.IndexingService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Setter

View File

@ -1,15 +1,17 @@
package com.iqser.red.service.peristence.v1.server.migration.migrations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.migration.Migration;
import com.iqser.red.service.peristence.v1.server.service.FileManagementStorageService;
import com.iqser.red.service.peristence.v1.server.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;

View File

@ -1,16 +1,18 @@
package com.iqser.red.service.peristence.v1.server.migration.migrations;
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.controller.ManualRedactionController;
import com.iqser.red.service.peristence.v1.server.migration.Migration;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.AddRedactionPersistenceService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Slf4j
@Setter

View File

@ -1,5 +1,15 @@
package com.iqser.red.service.peristence.v1.server.migration.migrations;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.controller.DictionaryController;
import com.iqser.red.service.peristence.v1.server.migration.Migration;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.BaseDictionaryEntry;
@ -7,17 +17,9 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.EntryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntryType;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Slf4j
@Setter

View File

@ -0,0 +1,27 @@
package com.iqser.red.service.peristence.v1.server.multitenancy;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class AsyncConfig extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("TenantAwareTaskExecutor-");
executor.setTaskDecorator(new TenantAwareTaskDecorator());
executor.initialize();
return executor;
}
}

View File

@ -0,0 +1,28 @@
package com.iqser.red.service.peristence.v1.server.multitenancy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import com.iqser.red.commons.spring.DefaultWebMvcConfiguration;
@Configuration
public class MultiTenancyWebConfiguration extends DefaultWebMvcConfiguration {
private final TenantInterceptor tenantInterceptor;
@Autowired
public MultiTenancyWebConfiguration(TenantInterceptor tenantInterceptor) {
this.tenantInterceptor = tenantInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addWebRequestInterceptor(tenantInterceptor);
}
}

View File

@ -0,0 +1,25 @@
package com.iqser.red.service.peristence.v1.server.multitenancy;
import org.springframework.core.task.TaskDecorator;
import org.springframework.lang.NonNull;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
public class TenantAwareTaskDecorator implements TaskDecorator {
@Override
@NonNull
public Runnable decorate(@NonNull Runnable runnable) {
String tenantId = TenantContext.getTenantId();
return () -> {
try {
TenantContext.setTenantId(tenantId);
runnable.run();
} finally {
TenantContext.setTenantId(null);
}
};
}
}

View File

@ -0,0 +1,41 @@
package com.iqser.red.service.peristence.v1.server.multitenancy;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
@Component
public class TenantInterceptor implements WebRequestInterceptor {
public static final String DEFAULT_TENANT = "redaction";
@Override
public void preHandle(WebRequest request) {
String tenantId = null;
if (request.getHeader("X-TENANT-ID") != null) {
tenantId = request.getHeader("X-TENANT-ID");
} else {
tenantId = DEFAULT_TENANT; // TODO Remove if multitenancy is fully integrated.
}
TenantContext.setTenantId(tenantId);
}
@Override
public void postHandle(WebRequest request, ModelMap model) {
TenantContext.clear();
}
@Override
public void afterCompletion(WebRequest request, Exception ex) {
}
}

View File

@ -0,0 +1,115 @@
package com.iqser.red.service.peristence.v1.server.multitenancy.persistence;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate5.SpringBeanContainer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.zaxxer.hikari.HikariDataSource;
import liquibase.integration.spring.SpringLiquibase;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableJpaRepositories(basePackageClasses = TenantRepository.class, entityManagerFactoryRef = "masterEntityManagerFactory", transactionManagerRef = "masterTransactionManager")
@EnableConfigurationProperties({DataSourceProperties.class, JpaProperties.class})
@RequiredArgsConstructor
public class MasterPersistenceConfig {
private final ConfigurableListableBeanFactory beanFactory;
private final JpaProperties jpaProperties;
private final String entityPackages = "com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity";
@Bean
public LocalContainerEntityManagerFactoryBean masterEntityManagerFactory(
@Qualifier("masterDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceUnitName("master-persistence-unit");
em.setPackagesToScan(entityPackages);
em.setDataSource(dataSource);
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Map<String, Object> properties = new HashMap<>(this.jpaProperties.getProperties());
properties.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
properties.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
properties.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(this.beanFactory));
em.setJpaPropertyMap(properties);
return em;
}
@Bean
public JpaTransactionManager masterTransactionManager(
@Qualifier("masterEntityManagerFactory") EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
@Bean
@ConfigurationProperties("multitenancy.master.liquibase")
public LiquibaseProperties masterLiquibaseProperties() {
return new LiquibaseProperties();
}
@Bean
public SpringLiquibase masterLiquibase(@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource) {
LiquibaseProperties liquibaseProperties = masterLiquibaseProperties();
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(liquibaseDataSource.getIfAvailable());
liquibase.setChangeLog(liquibaseProperties.getChangeLog());
liquibase.setContexts(liquibaseProperties.getContexts());
return liquibase;
}
@Bean
@ConfigurationProperties("multitenancy.master.datasource")
public DataSourceProperties masterDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@LiquibaseDataSource
@ConfigurationProperties("multitenancy.master.datasource.hikari")
public DataSource masterDataSource() {
HikariDataSource dataSource = masterDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
dataSource.setPoolName("masterDataSource");
return dataSource;
}
}

View File

@ -0,0 +1,97 @@
package com.iqser.red.service.peristence.v1.server.multitenancy.persistence;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cfg.AvailableSettings;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate5.SpringBeanContainer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import com.iqser.red.service.persistence.management.v1.processor.service.EncryptionDecryptionService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.CurrentTenantIdentifierResolverImpl;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.DynamicDataSourceBasedMultiTenantConnectionProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ColorsRepository;
import lombok.RequiredArgsConstructor;
@Configuration
@EnableJpaRepositories(basePackageClasses = ColorsRepository.class, entityManagerFactoryRef = "tenantEntityManagerFactory", transactionManagerRef = "tenantTransactionManager")
@EnableConfigurationProperties(JpaProperties.class)
@RequiredArgsConstructor
public class TenantPersistenceConfig {
private final ConfigurableListableBeanFactory beanFactory;
private final JpaProperties jpaProperties;
private final String entityPackages = "com.iqser.red.service.persistence.management.v1.processor.entity";
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean tenantEntityManagerFactory(
DynamicDataSourceBasedMultiTenantConnectionProvider connectionProvider,
CurrentTenantIdentifierResolverImpl tenantResolver) {
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setPersistenceUnitName("tenant-persistence-unit");
emfBean.setPackagesToScan(entityPackages);
emfBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Map<String, Object> properties = new HashMap<>(this.jpaProperties.getProperties());
properties.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
properties.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
properties.put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(this.beanFactory));
properties.put(AvailableSettings.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
properties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
properties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver);
emfBean.setJpaPropertyMap(properties);
return emfBean;
}
@Primary
@Bean
public JpaTransactionManager tenantTransactionManager(
@Qualifier("tenantEntityManagerFactory") EntityManagerFactory emf) {
JpaTransactionManager tenantTransactionManager = new JpaTransactionManager();
tenantTransactionManager.setEntityManagerFactory(emf);
return tenantTransactionManager;
}
@Bean
@ConfigurationProperties("multitenancy.tenant.liquibase")
public LiquibaseProperties tenantLiquibaseProperties() {
return new LiquibaseProperties();
}
@Bean
@DependsOn("masterLiquibase")
public TenantSpringLiquibaseExecutor tenantLiquibase(EncryptionDecryptionService encryptionService,
TenantRepository tenantRepository,
LiquibaseProperties tenantLiquibaseProperties) {
return new TenantSpringLiquibaseExecutor(encryptionService, tenantRepository, tenantLiquibaseProperties);
}
}

View File

@ -0,0 +1,74 @@
package com.iqser.red.service.peristence.v1.server.multitenancy.persistence;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.TenantEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.EncryptionDecryptionService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import liquibase.integration.spring.SpringLiquibase;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
public class TenantSpringLiquibaseExecutor implements InitializingBean, ResourceLoaderAware {
private final EncryptionDecryptionService encryptionService;
private final TenantRepository tenantRepository;
@Qualifier("tenantLiquibaseProperties")
private final LiquibaseProperties tenantLiquibaseProperties;
@Setter
private ResourceLoader resourceLoader;
@Override
public void afterPropertiesSet() {
log.info("DynamicDataSources based multitenancy enabled");
this.runOnAllTenants(tenantRepository.findAll());
}
@SneakyThrows
protected void runOnAllTenants(List<TenantEntity> tenants) {
for (TenantEntity tenant : tenants) {
log.info("Initializing Liquibase for tenant " + tenant.getTenantId());
try (Connection connection = DriverManager.getConnection(tenant.getJdbcUrl(), tenant.getUser(), encryptionService.decrypt(tenant.getPassword()))) {
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
SpringLiquibase liquibase = this.getSpringLiquibase(tenantDataSource);
liquibase.afterPropertiesSet();
}
log.info("Liquibase ran for tenant " + tenant.getTenantId());
}
}
protected SpringLiquibase getSpringLiquibase(DataSource dataSource) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setResourceLoader(resourceLoader);
liquibase.setDataSource(dataSource);
liquibase.setChangeLog(tenantLiquibaseProperties.getChangeLog());
liquibase.setContexts(tenantLiquibaseProperties.getContexts());
return liquibase;
}
}

View File

@ -1,5 +1,15 @@
package com.iqser.red.service.peristence.v1.server.service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierTemplateEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
@ -12,12 +22,9 @@ import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.Do
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.DossierTemplateStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionarySummary;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionarySummaryResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@ -49,7 +56,7 @@ public class DossierTemplateStatsService {
List<DossierTemplateEntity> templates = allUnDeletedTemplates.stream().filter(dt -> !DossierTemplateStatus.INCOMPLETE.equals(dt.getDossierTemplateStatus())).collect(Collectors.toList());
final List<DossierTemplateStats> dossierTemplateStatsList = Collections.synchronizedList(new ArrayList<>());
templates.parallelStream().forEach(dt -> dossierTemplateStatsList.add(getDossierTemplateStats(dt.getId(), dt.getName(), dt.getDossierTemplateStatus())));
templates.stream().forEach(dt -> dossierTemplateStatsList.add(getDossierTemplateStats(dt.getId(), dt.getName(), dt.getDossierTemplateStatus())));
List<DossierTemplateStats> result = dossierTemplateStatsList.stream().filter(stat -> !(DossierTemplateStatus.INACTIVE.equals(stat.getDossierTemplateStatus())
&& stat.getNumberOfActiveDossiers() == 0 && stat.getNumberOfArchivedDossiers() == 0)).collect(Collectors.toList());

View File

@ -0,0 +1,87 @@
package com.iqser.red.service.peristence.v1.server.service;
import java.sql.Connection;
import java.sql.DriverManager;
import javax.sql.DataSource;
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.datasource.SingleConnectionDataSource;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.multitenancy.entity.TenantEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.EncryptionDecryptionService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.service.v1.api.model.multitenancy.Tenant;
import liquibase.exception.LiquibaseException;
import liquibase.integration.spring.SpringLiquibase;
import lombok.SneakyThrows;
@Service
@EnableConfigurationProperties(LiquibaseProperties.class)
public class TenantManagementService {
private final EncryptionDecryptionService encryptionService;
private final LiquibaseProperties liquibaseProperties;
private final ResourceLoader resourceLoader;
private final TenantRepository tenantRepository;
public TenantManagementService(EncryptionDecryptionService encryptionService,
@Qualifier("tenantLiquibaseProperties") LiquibaseProperties liquibaseProperties,
ResourceLoader resourceLoader, TenantRepository tenantRepository) {
this.encryptionService = encryptionService;
this.liquibaseProperties = liquibaseProperties;
this.resourceLoader = resourceLoader;
this.tenantRepository = tenantRepository;
}
@SneakyThrows
public void createTenant(Tenant tenant) {
if (!tenantRepository.findById(tenant.getTenantId()).isPresent()) {
String encryptedPassword = encryptionService.encrypt(tenant.getPassword());
try (Connection connection = DriverManager.getConnection(tenant.getJdbcUrl(), tenant.getUser(), tenant.getPassword())) {
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
runLiquibase(tenantDataSource);
}
TenantEntity tenantEntity = TenantEntity.builder()
.tenantId(tenant.getTenantId())
.user(tenant.getUser())
.jdbcUrl(tenant.getJdbcUrl())
.password(encryptedPassword)
.build();
tenantRepository.save(tenantEntity);
}
}
private void runLiquibase(DataSource dataSource) throws LiquibaseException {
SpringLiquibase liquibase = getSpringLiquibase(dataSource);
liquibase.afterPropertiesSet();
}
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;
}
}

View File

@ -1,20 +1,22 @@
package com.iqser.red.service.peristence.v1.server.service.job;
import com.iqser.red.service.peristence.v1.server.configuration.MessagingConfiguration;
import com.iqser.red.service.peristence.v1.server.service.FileStatusService;
import com.iqser.red.service.peristence.v1.server.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.Comparator;
import java.util.List;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.iqser.red.service.peristence.v1.server.configuration.MessagingConfiguration;
import com.iqser.red.service.peristence.v1.server.service.FileStatusService;
import com.iqser.red.service.peristence.v1.server.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@ -24,6 +26,7 @@ public class AutomaticAnalysisJob implements Job {
private final AmqpAdmin amqpAdmin;
private final FileManagementServiceSettings fileManagementServiceSettings;
private final FileStatusService fileStatusService;
private final TenantRepository tenantRepository;
@Override
@ -34,42 +37,48 @@ public class AutomaticAnalysisJob implements Job {
return;
}
var redactionQueueInfo = amqpAdmin.getQueueInfo(MessagingConfiguration.REDACTION_QUEUE);
if (redactionQueueInfo != null) {
log.info("Checking queue status to see if background analysis can happen. Currently {} holds {} elements and has {} consumers", MessagingConfiguration.REDACTION_QUEUE, redactionQueueInfo
.getMessageCount(), redactionQueueInfo.getConsumerCount());
// only 1 file in queue
var consumerCount = redactionQueueInfo.getConsumerCount();
if (redactionQueueInfo.getMessageCount() <= consumerCount * 5) {
// queue up 5 files
var allStatuses = getAllRelevantStatuses();
allStatuses.sort(Comparator.comparing(FileModel::getLastUpdated));
tenantRepository.findAll().forEach(tenant -> {
var allStatusesIterator = allStatuses.iterator();
log.info("Files that require reanalysis: {}", allStatuses.size());
TenantContext.setTenantId(tenant.getTenantId());
var queuedFiles = 0;
var redactionQueueInfo = amqpAdmin.getQueueInfo(MessagingConfiguration.REDACTION_QUEUE);
if (redactionQueueInfo != null) {
log.info("Checking queue status to see if background analysis can happen. Currently {} holds {} elements and has {} consumers", MessagingConfiguration.REDACTION_QUEUE, redactionQueueInfo.getMessageCount(), redactionQueueInfo.getConsumerCount());
// only 1 file in queue
var consumerCount = redactionQueueInfo.getConsumerCount();
if (redactionQueueInfo.getMessageCount() <= consumerCount * 5) {
// queue up 5 files
var allStatuses = getAllRelevantStatuses();
while (queuedFiles < (consumerCount * 5) && allStatusesIterator.hasNext()) {
var next = allStatusesIterator.next();
// in case the file doesn't have numberOfPages set, we assume an average.
allStatuses.sort(Comparator.comparing(FileModel::getLastUpdated));
if (next.isFullAnalysisRequired()) {
log.info("Queued file: {} for automatic full analysis! ", next.getFilename());
fileStatusService.setStatusFullReprocess(next.getDossierId(), next.getId(), false, false);
} else if (next.isReanalysisRequired()) {
log.info("Queued file: {} for automatic reanalysis! ", next.getFilename());
fileStatusService.setStatusReprocess(next.getDossierId(), next.getId(), false);
var allStatusesIterator = allStatuses.iterator();
log.info("Files that require reanalysis: {}", allStatuses.size());
var queuedFiles = 0;
while (queuedFiles < (consumerCount * 5) && allStatusesIterator.hasNext()) {
var next = allStatusesIterator.next();
// in case the file doesn't have numberOfPages set, we assume an average.
if (next.isFullAnalysisRequired()) {
log.info("Queued file: {} for automatic full analysis! ", next.getFilename());
fileStatusService.setStatusFullReprocess(next.getDossierId(), next.getId(), false, false);
} else if (next.isReanalysisRequired()) {
log.info("Queued file: {} for automatic reanalysis! ", next.getFilename());
fileStatusService.setStatusReprocess(next.getDossierId(), next.getId(), false);
}
queuedFiles++;
}
queuedFiles++;
}
} else {
log.info("Failed to obtain queue info for queue: {}", MessagingConfiguration.REDACTION_QUEUE);
}
} else {
log.info("Failed to obtain queue info for queue: {}", MessagingConfiguration.REDACTION_QUEUE);
}
});
}

View File

@ -3,7 +3,6 @@ package com.iqser.red.service.peristence.v1.server.service.job;
import java.time.OffsetDateTime;
import java.util.List;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service;
@ -12,6 +11,9 @@ import com.iqser.red.service.peristence.v1.server.service.DossierService;
import com.iqser.red.service.peristence.v1.server.service.FileService;
import com.iqser.red.service.peristence.v1.server.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -25,33 +27,39 @@ public class DeletedFilesCleanupJob implements Job {
private final FileStatusService fileStatusService;
private final FileService fileService;
private final ApplicationConfigService applicationConfigService;
private final TenantRepository tenantRepository;
@Override
public void execute(JobExecutionContext jobExecutionContext) {
var now = OffsetDateTime.now();
List<DossierEntity> dossiers = dossierService.getAllDossiers();
var applicationConfigurationEntity = applicationConfigService.getApplicationConfig();
tenantRepository.findAll().forEach(tenant -> {
for (DossierEntity dossierEntity : dossiers) {
if (dossierEntity.getSoftDeletedTime() != null && dossierEntity.getHardDeletedTime() == null) {
if (dossierEntity.getSoftDeletedTime().isBefore(now.minusHours(applicationConfigurationEntity.getSoftDeleteCleanupTime()))) {
dossierService.hardDeleteDossier(dossierEntity.getId());
log.info("Hard deleted dossier with dossier id {} ", dossierEntity.getId());
}
} else {
var files = fileStatusService.getDossierStatus(dossierEntity.getId());
for (var file : files) {
if (file.getHardDeletedTime() == null && file.getDeleted() != null && file.getDeleted()
.isBefore(now.minusHours(applicationConfigurationEntity.getSoftDeleteCleanupTime()))) {
fileService.hardDeleteFile(dossierEntity.getId(), file.getId());
fileStatusService.setFileStatusHardDeleted(file.getId());
log.info("Hard deleted file with dossier id {} and file id {}", dossierEntity.getId(), file.getId());
TenantContext.setTenantId(tenant.getTenantId());
var now = OffsetDateTime.now();
List<DossierEntity> dossiers = dossierService.getAllDossiers();
var applicationConfigurationEntity = applicationConfigService.getApplicationConfig();
for (DossierEntity dossierEntity : dossiers) {
if (dossierEntity.getSoftDeletedTime() != null && dossierEntity.getHardDeletedTime() == null) {
if (dossierEntity.getSoftDeletedTime().isBefore(now.minusHours(applicationConfigurationEntity.getSoftDeleteCleanupTime()))) {
dossierService.hardDeleteDossier(dossierEntity.getId());
log.info("Hard deleted dossier with dossier id {} ", dossierEntity.getId());
}
} else {
var files = fileStatusService.getDossierStatus(dossierEntity.getId());
for (var file : files) {
if (file.getHardDeletedTime() == null && file.getDeleted() != null && file.getDeleted().isBefore(now.minusHours(applicationConfigurationEntity.getSoftDeleteCleanupTime()))) {
fileService.hardDeleteFile(dossierEntity.getId(), file.getId());
fileStatusService.setFileStatusHardDeleted(file.getId());
log.info("Hard deleted file with dossier id {} and file id {}", dossierEntity.getId(), file.getId());
}
}
}
}
}
});
}

View File

@ -1,19 +1,23 @@
package com.iqser.red.service.peristence.v1.server.service.job;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.service.DossierService;
import com.iqser.red.service.persistence.management.v1.processor.entity.download.DownloadStatusEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import com.iqser.red.storage.commons.service.StorageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
@Slf4j
@RequiredArgsConstructor
@ -24,35 +28,39 @@ public class DownloadCleanupJob implements Job {
private final StorageService storageService;
private final DossierService dossierService;
private final ApplicationConfigService applicationConfigService;
private final TenantRepository tenantRepository;
@Override
public void execute(JobExecutionContext jobExecutionContext) {
var now = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
log.info("Checking for downloads to delete at {}", now);
List<DownloadStatusEntity> downloadStatusList = downloadStatusPersistenceService.getStatus();
downloadStatusList.forEach(downloadStatus -> {
tenantRepository.findAll().forEach(tenant -> {
var dossier = downloadStatus.getDossier();
var applicationConfigurationEntity = applicationConfigService.getApplicationConfig();
if (downloadStatus.getLastDownload() != null && downloadStatus.getLastDownload()
.plusHours(applicationConfigurationEntity.getDownloadCleanupDownloadFilesHours())
.isBefore(now)) {
log.info("1. Deleting download status {} because DownloadCleanupDownloadFilesHours is {} and c+h {} is after {}", downloadStatus,
applicationConfigurationEntity.getDownloadCleanupDownloadFilesHours(), downloadStatus.getCreationDate()
.plusHours(applicationConfigurationEntity.getDownloadCleanupDownloadFilesHours()), now);
deleteDownload(downloadStatus);
} else if (downloadStatus.getLastDownload() == null && downloadStatus.getCreationDate()
.plusHours(applicationConfigurationEntity.getDownloadCleanupNotDownloadFilesHours())
.isBefore(now)) {
log.info("2. Deleting download status {} because DownloadCleanupNotDownloadFilesHours is {} and c+h {} is after {}", downloadStatus,
applicationConfigurationEntity.getDownloadCleanupNotDownloadFilesHours(), downloadStatus.getCreationDate()
.plusHours(applicationConfigurationEntity.getDownloadCleanupNotDownloadFilesHours()), now);
deleteDownload(downloadStatus);
} else if (dossierService.getDossierById(dossier.getId()).getSoftDeletedTime() != null) {
log.info("3. Deleting download {}, because dossier does not exist", downloadStatus.getStorageId());
deleteDownload(downloadStatus);
}
TenantContext.setTenantId(tenant.getTenantId());
var now = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
log.info("Checking for downloads to delete at {}", now);
List<DownloadStatusEntity> downloadStatusList = downloadStatusPersistenceService.getStatus();
downloadStatusList.forEach(downloadStatus -> {
var dossier = downloadStatus.getDossier();
var applicationConfigurationEntity = applicationConfigService.getApplicationConfig();
if (downloadStatus.getLastDownload() != null && downloadStatus.getLastDownload()
.plusHours(applicationConfigurationEntity.getDownloadCleanupDownloadFilesHours())
.isBefore(now)) {
log.info("1. Deleting download status {} because DownloadCleanupDownloadFilesHours is {} and c+h {} is after {}", downloadStatus, applicationConfigurationEntity.getDownloadCleanupDownloadFilesHours(), downloadStatus.getCreationDate()
.plusHours(applicationConfigurationEntity.getDownloadCleanupDownloadFilesHours()), now);
deleteDownload(downloadStatus);
} else if (downloadStatus.getLastDownload() == null && downloadStatus.getCreationDate()
.plusHours(applicationConfigurationEntity.getDownloadCleanupNotDownloadFilesHours())
.isBefore(now)) {
log.info("2. Deleting download status {} because DownloadCleanupNotDownloadFilesHours is {} and c+h {} is after {}", downloadStatus, applicationConfigurationEntity.getDownloadCleanupNotDownloadFilesHours(), downloadStatus.getCreationDate()
.plusHours(applicationConfigurationEntity.getDownloadCleanupNotDownloadFilesHours()), now);
deleteDownload(downloadStatus);
} else if (dossierService.getDossierById(dossier.getId()).getSoftDeletedTime() != null) {
log.info("3. Deleting download {}, because dossier does not exist", downloadStatus.getStorageId());
deleteDownload(downloadStatus);
}
});
});
}

View File

@ -1,19 +1,23 @@
package com.iqser.red.service.peristence.v1.server.service.job;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationEmailService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPreferencesPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.model.notification.EmailNotificationType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service;
import java.time.DayOfWeek;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationEmailService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPreferencesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import com.iqser.red.service.persistence.service.v1.api.model.notification.EmailNotificationType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@Service
@ -22,37 +26,41 @@ public class SendNotificationEmailJob implements Job {
private final NotificationEmailService notificationEmailService;
private final NotificationPersistenceService notificationPersistenceService;
private final NotificationPreferencesPersistenceService notificationPreferencesPersistenceService;
private final TenantRepository tenantRepository;
@Override
public void execute(JobExecutionContext jobExecutionContext) {
tenantRepository.findAll().forEach(tenant -> {
var allConfiguredPreferences = notificationPreferencesPersistenceService.findAll();
allConfiguredPreferences.forEach(preference -> {
if (preference.isEmailNotificationsEnabled() && preference.getEmailNotificationType() == EmailNotificationType.WEEKLY_SUMMARY || preference.getEmailNotificationType() == EmailNotificationType.DAILY_SUMMARY) {
TenantContext.setTenantId(tenant.getTenantId());
var now = OffsetDateTime.now().truncatedTo(ChronoUnit.HOURS);
var allConfiguredPreferences = notificationPreferencesPersistenceService.findAll();
allConfiguredPreferences.forEach(preference -> {
if (preference.isEmailNotificationsEnabled() && preference.getEmailNotificationType() == EmailNotificationType.WEEKLY_SUMMARY || preference.getEmailNotificationType() == EmailNotificationType.DAILY_SUMMARY) {
// Weekly summary is sent monday night
if (now.getDayOfWeek() == DayOfWeek.MONDAY && preference.getEmailNotificationType() == EmailNotificationType.WEEKLY_SUMMARY) {
var from = OffsetDateTime.now().minusDays(7).truncatedTo(ChronoUnit.HOURS).withHour(0);
var to = OffsetDateTime.now().truncatedTo(ChronoUnit.HOURS).withHour(0);
var notifications = notificationPersistenceService.getNotificationsForEmailSummary(preference.getUserId(), from, to);
var now = OffsetDateTime.now().truncatedTo(ChronoUnit.HOURS);
notificationEmailService.sendNotificationEmail(preference.getUserId(), EmailNotificationType.WEEKLY_SUMMARY, notifications);
} else {
var from = OffsetDateTime.now().minusDays(1).withHour(0).truncatedTo(ChronoUnit.HOURS);
var to = OffsetDateTime.now().minusDays(1).withHour(23).truncatedTo(ChronoUnit.HOURS);
var notifications = notificationPersistenceService.getNotificationsForEmailSummary(preference.getUserId(), from, to);
// Weekly summary is sent monday night
if (now.getDayOfWeek() == DayOfWeek.MONDAY && preference.getEmailNotificationType() == EmailNotificationType.WEEKLY_SUMMARY) {
var from = OffsetDateTime.now().minusDays(7).truncatedTo(ChronoUnit.HOURS).withHour(0);
var to = OffsetDateTime.now().truncatedTo(ChronoUnit.HOURS).withHour(0);
var notifications = notificationPersistenceService.getNotificationsForEmailSummary(preference.getUserId(), from, to);
notificationEmailService.sendNotificationEmail(preference.getUserId(), EmailNotificationType.DAILY_SUMMARY, notifications);
notificationEmailService.sendNotificationEmail(preference.getUserId(), EmailNotificationType.WEEKLY_SUMMARY, notifications);
} else {
var from = OffsetDateTime.now().minusDays(1).withHour(0).truncatedTo(ChronoUnit.HOURS);
var to = OffsetDateTime.now().minusDays(1).withHour(23).truncatedTo(ChronoUnit.HOURS);
var notifications = notificationPersistenceService.getNotificationsForEmailSummary(preference.getUserId(), from, to);
notificationEmailService.sendNotificationEmail(preference.getUserId(), EmailNotificationType.DAILY_SUMMARY, notifications);
}
}
}
});
});
}
}

View File

@ -12,17 +12,6 @@ server:
spring:
main:
allow-circular-references: true # FIXME
datasource:
url: jdbc:postgresql://${PSQL_HOST:localhost}:${PSQL_PORT:5432}/${PSQL_DATABASE:redaction}?cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true
driverClassName: org.postgresql.Driver
username: ${PSQL_USERNAME:redaction}
password: ${PSQL_PASSWORD:redaction}
platform: org.hibernate.dialect.PostgreSQL95Dialect
hikari:
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
jpa:
database-platform: org.hibernate.dialect.PostgreSQL95Dialect
hibernate:
@ -89,4 +78,31 @@ storage:
endpoint: 'https://s3.amazonaws.com'
backend: 's3'
multitenancy:
datasource-cache:
maximumSize: 100
expireAfterAccess: 1
master:
datasource:
url: jdbc:postgresql://${PSQL_HOST:localhost}:${PSQL_PORT:5432}/${PSQL_DATABASE:master}?cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true
driverClassName: org.postgresql.Driver
username: ${PSQL_USERNAME:redaction}
password: ${PSQL_PASSWORD:redaction}
platform: org.hibernate.dialect.PostgreSQL95Dialect
hikari:
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
liquibase:
changeLog: classpath:db/changelog/db.changelog-master.yaml
tenant:
datasource:
driverClassName: org.postgresql.Driver
hikari:
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
liquibase:
changeLog: classpath:db/changelog/db.changelog-tenant.yaml

View File

@ -1,101 +1,3 @@
databaseChangeLog:
- include:
file: db/changelog/1-initial-schema.changelog.yaml
- include:
file: db/changelog/2-ignored-hint-color.changelog.yaml
- include:
file: db/changelog/3-added-annotation-modification-date.changelog.yaml
- include:
file: db/changelog/4-archived-dossier.changelog.yaml
- include:
file: db/changelog/5-excluded-from-automatic-analysis-file-column.changelog.yaml
- include:
file: db/changelog/6-dossier-status-table.changelog.yaml
- include:
file: db/changelog/7-json-column-mapping.changelog.yaml
- include:
file: db/changelog/sql/7.1-set-json-fields.sql
- include:
file: db/changelog/sql/7.2-set-dossier-status.sql
- include:
file: db/changelog/9-changed-annotation-modification-date.changelog.yaml
- include:
file: db/changelog/10-added-file-manipulation-date.changelog.yaml
- include:
file: db/changelog/sql/10-set-file-manipulation-date.sql
- include:
file: db/changelog/11-added-dictionary_false_positive_tables.changelog.yaml
- include:
file: db/changelog/12-dossier-visibility.changelog.yaml
- include:
file: db/changelog/12-added-rank-dossier-status.changelog.yaml
- include:
file: db/changelog/13-file-manual-change-date.changelog.yaml
- include:
file: db/changelog/release-3.2.0/1-add-index-on-dictionary-entry.changelog.yaml
- include:
file: db/changelog/14-add-redaction-source-id.changelog.yaml
- include:
file: db/changelog/15-dossier-remove-dossier-state.changelog.yaml
- include:
file: db/changelog/16-added-has-dictionary-to-entities.changelog.yaml
- include:
file: db/changelog/17-digital-signature-kms.changelog.yaml
- include:
file: db/changelog/18-add-migration-table.yaml
- include:
file: db/changelog/19-added-has-highlights-to-file.changelog.yaml
- include:
file: db/changelog/20-add-index-information-table.yaml
- include:
file: db/changelog/21-added-auto-hide-skipped-to-entities.changelog.yaml
- include:
file: db/changelog/sql/22-update-file-dossier-attributes.sql
- include:
file: db/changelog/22-add-soft-delete-time-to-entity.changelog.yaml
- include:
file: db/changelog/23-quartz.changelog.yaml
- include:
file: db/changelog/sql/24.0-clean-up-duplicate-dictionary-entries.sql
- include:
file: db/changelog/24-add-unique-constraint-for-dictionary.yaml
- include:
file: db/changelog/release-3.3.12/1-fix-dossier-dictionary.sql
- include:
file: db/changelog/25-add-index-to-dictionary-entry-tables.yaml
- include:
file: db/changelog/26-application-config-table.changelog.yaml
- include:
file: db/changelog/sql/26-initiliaze-application-configuration-table.sql
- include:
file: db/changelog/sql/27-update-soft-delete-date.sql
- include:
file: db/changelog/28-add-update-dictionary-to-manual-resize-redactions.yaml
- include:
file: db/changelog/sql/30-change-bigint-to-serial.sql
- include:
file: db/changelog/31-add-file-size-column.changelog.yaml
- include:
file: db/changelog/sql/31-watermark-configuration.sql
- include:
file: db/changelog/32-added-skipped-color-type-table.changelog.yaml
- include:
file: db/changelog/33-add-file-processing-error-counter-column.changelog.yaml
- include:
file: db/changelog/sql/33-set-file-processing-error-counter.sql
- include:
file: db/changelog/34-add-reports-information-column.changelog.yaml
- include:
file: db/changelog/sql/35-update-skipped-color-existing-types.sql
- include:
file: db/changelog/36-revert-reports-information-column.changelog.yaml
- include:
file: db/changelog/sql/37-update-colors.sql
- include:
file: db/changelog/sql/38-update-soft-deleted-processed-flag.sql
- include:
file: db/changelog/sql/39-update-manual-redaction-source-id.sql
- include:
file: db/changelog/release-3.3.13/1-update-soft-deleted-processed-flag.sql
- include:
file: db/changelog/release-3.3.14/1-bump-rules-version.sql
file: db/changelog/master/1-initial-schema.changelog.yaml

View File

@ -0,0 +1,101 @@
databaseChangeLog:
- include:
file: db/changelog/tenant/1-initial-schema.changelog.yaml
- include:
file: db/changelog/tenant/2-ignored-hint-color.changelog.yaml
- include:
file: db/changelog/tenant/3-added-annotation-modification-date.changelog.yaml
- include:
file: db/changelog/tenant/4-archived-dossier.changelog.yaml
- include:
file: db/changelog/tenant/5-excluded-from-automatic-analysis-file-column.changelog.yaml
- include:
file: db/changelog/tenant/6-dossier-status-table.changelog.yaml
- include:
file: db/changelog/tenant/7-json-column-mapping.changelog.yaml
- include:
file: db/changelog/tenant/sql/7.1-set-json-fields.sql
- include:
file: db/changelog/tenant/sql/7.2-set-dossier-status.sql
- include:
file: db/changelog/tenant/9-changed-annotation-modification-date.changelog.yaml
- include:
file: db/changelog/tenant/10-added-file-manipulation-date.changelog.yaml
- include:
file: db/changelog/tenant/sql/10-set-file-manipulation-date.sql
- include:
file: db/changelog/tenant/11-added-dictionary_false_positive_tables.changelog.yaml
- include:
file: db/changelog/tenant/12-dossier-visibility.changelog.yaml
- include:
file: db/changelog/tenant/12-added-rank-dossier-status.changelog.yaml
- include:
file: db/changelog/tenant/13-file-manual-change-date.changelog.yaml
- include:
file: db/changelog/tenant/release-3.2.0/1-add-index-on-dictionary-entry.changelog.yaml
- include:
file: db/changelog/tenant/14-add-redaction-source-id.changelog.yaml
- include:
file: db/changelog/tenant/15-dossier-remove-dossier-state.changelog.yaml
- include:
file: db/changelog/tenant/16-added-has-dictionary-to-entities.changelog.yaml
- include:
file: db/changelog/tenant/17-digital-signature-kms.changelog.yaml
- include:
file: db/changelog/tenant/18-add-migration-table.yaml
- include:
file: db/changelog/tenant/19-added-has-highlights-to-file.changelog.yaml
- include:
file: db/changelog/tenant/20-add-index-information-table.yaml
- include:
file: db/changelog/tenant/21-added-auto-hide-skipped-to-entities.changelog.yaml
- include:
file: db/changelog/tenant/sql/22-update-file-dossier-attributes.sql
- include:
file: db/changelog/tenant/22-add-soft-delete-time-to-entity.changelog.yaml
- include:
file: db/changelog/tenant/23-quartz.changelog.yaml
- include:
file: db/changelog/tenant/sql/24.0-clean-up-duplicate-dictionary-entries.sql
- include:
file: db/changelog/tenant/24-add-unique-constraint-for-dictionary.yaml
- include:
file: db/changelog/tenant/release-3.3.12/1-fix-dossier-dictionary.sql
- include:
file: db/changelog/tenant/25-add-index-to-dictionary-entry-tables.yaml
- include:
file: db/changelog/tenant/26-application-config-table.changelog.yaml
- include:
file: db/changelog/tenant/sql/26-initiliaze-application-configuration-table.sql
- include:
file: db/changelog/tenant/sql/27-update-soft-delete-date.sql
- include:
file: db/changelog/tenant/28-add-update-dictionary-to-manual-resize-redactions.yaml
- include:
file: db/changelog/tenant/sql/30-change-bigint-to-serial.sql
- include:
file: db/changelog/tenant/31-add-file-size-column.changelog.yaml
- include:
file: db/changelog/tenant/sql/31-watermark-configuration.sql
- include:
file: db/changelog/tenant/32-added-skipped-color-type-table.changelog.yaml
- include:
file: db/changelog/tenant/33-add-file-processing-error-counter-column.changelog.yaml
- include:
file: db/changelog/tenant/sql/33-set-file-processing-error-counter.sql
- include:
file: db/changelog/tenant/34-add-reports-information-column.changelog.yaml
- include:
file: db/changelog/tenant/sql/35-update-skipped-color-existing-types.sql
- include:
file: db/changelog/tenant/36-revert-reports-information-column.changelog.yaml
- include:
file: db/changelog/tenant/sql/37-update-colors.sql
- include:
file: db/changelog/tenant/sql/38-update-soft-deleted-processed-flag.sql
- include:
file: db/changelog/tenant/sql/39-update-manual-redaction-source-id.sql
- include:
file: db/changelog/tenant/release-3.3.13/1-update-soft-deleted-processed-flag.sql
- include:
file: db/changelog/tenant/release-3.3.14/1-bump-rules-version.sql

View File

@ -0,0 +1,24 @@
databaseChangeLog:
- changeSet:
id: create-tenants-table
author: dom
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: tenant_pkey
name: tenant_id
type: VARCHAR(255)
- column:
name: username
type: VARCHAR(255)
- column:
name: password
type: VARCHAR(255)
- column:
name: jdbc_url
type: VARCHAR(255)
tableName: tenant

View File

@ -0,0 +1,11 @@
package com.iqser.red.service.peristence.v1.server.integration.client;
import org.springframework.cloud.openfeign.FeignClient;
import com.iqser.red.service.persistence.service.v1.api.resources.TenantsResource;
@FeignClient(name = "TenantsClient", url = "http://localhost:${server.port}")
public interface TenantsClient extends TenantsResource {
}

View File

@ -1,7 +1,26 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.collect.Sets;
import com.iqser.red.service.peristence.v1.server.integration.client.*;
import com.iqser.red.service.peristence.v1.server.integration.client.DictionaryClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierStatusClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateStatsClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.client.UploadClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.FileTesterAndProvider;
@ -16,18 +35,6 @@ import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ty
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionarySummary;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.Type;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTest {
private static final String TYPE_ID_1 = "type1";
@ -239,6 +246,8 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
@Test
public void testDossierTemplateStats() {
for (int i = 0; i < 2; i++) {
var template = dossierTemplateTesterAndProvider.provideTestTemplate("test template: " + i);

View File

@ -1,5 +1,12 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.iqser.red.service.peristence.v1.server.integration.client.DictionaryClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
@ -8,12 +15,6 @@ import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPers
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.Type;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import static org.assertj.core.api.Assertions.assertThat;
public class TypeTest extends AbstractPersistenceServerServiceTest {

View File

@ -1,23 +1,13 @@
package com.iqser.red.service.peristence.v1.server.integration.utils;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ByteContentDocument;
import com.iqser.red.service.pdftron.redaction.v1.api.model.DocumentRequest;
import com.iqser.red.service.pdftron.redaction.v1.api.model.UntouchedDocumentResponse;
import com.iqser.red.service.peristence.v1.server.Application;
import com.iqser.red.service.peristence.v1.server.client.RedactionClient;
import com.iqser.red.service.peristence.v1.server.client.SearchClient;
import com.iqser.red.service.peristence.v1.server.integration.client.ApplicationConfigClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.utils.MetricsPrinterService;
import com.iqser.red.service.peristence.v1.server.utils.StorageIdUtils;
import com.iqser.red.service.persistence.management.v1.processor.client.PDFTronRedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.*;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.ApplicationConfig;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.RedactionResult;
import com.iqser.red.storage.commons.StorageAutoConfiguration;
import com.iqser.red.storage.commons.service.StorageService;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import java.sql.Connection;
import java.sql.DriverManager;
import javax.sql.DataSource;
import org.assertj.core.util.Lists;
import org.junit.After;
import org.junit.Before;
@ -34,20 +24,77 @@ import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.StatementCallback;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ByteContentDocument;
import com.iqser.red.service.pdftron.redaction.v1.api.model.DocumentRequest;
import com.iqser.red.service.peristence.v1.server.Application;
import com.iqser.red.service.peristence.v1.server.client.RedactionClient;
import com.iqser.red.service.peristence.v1.server.client.SearchClient;
import com.iqser.red.service.peristence.v1.server.integration.client.ApplicationConfigClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.client.TenantsClient;
import com.iqser.red.service.peristence.v1.server.utils.MetricsPrinterService;
import com.iqser.red.service.peristence.v1.server.utils.StorageIdUtils;
import com.iqser.red.service.persistence.management.v1.processor.client.PDFTronRedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.mulitenancy.repository.TenantRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ApplicationConfigRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.AuditRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DigitalSignatureRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierAttributeConfigRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierAttributeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierStatusRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DownloadStatusRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.EntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FalsePositiveEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FalseRecommendationEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileAttributeConfigRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileAttributesGeneralConfigurationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileAttributesRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ForceRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ImageRecategorizationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.IndexInformationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.LegalBasisChangeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.LegalBasisMappingRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ManualRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.NotificationPreferencesRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.NotificationRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.RemoveRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ReportTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.RuleSetRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.SMTPRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.TypeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.ViewedPagesRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.WatermarkRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.ApplicationConfig;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.model.multitenancy.Tenant;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.RedactionResult;
import com.iqser.red.storage.commons.StorageAutoConfiguration;
import com.iqser.red.storage.commons.service.StorageService;
import lombok.SneakyThrows;
@RunWith(SpringRunner.class)
@EnableFeignClients(basePackageClasses = FileClient.class)
@Import(AbstractPersistenceServerServiceTest.TestConfiguration.class)
@ContextConfiguration(initializers = {AbstractPersistenceServerServiceTest.Initializer.class})
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
properties = "spring-hibernate-query-utils.n-plus-one-queries-detection.error-level=INFO")
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = "spring-hibernate-query-utils.n-plus-one-queries-detection.error-level=INFO")
public abstract class AbstractPersistenceServerServiceTest {
@MockBean
@ -132,11 +179,26 @@ public abstract class AbstractPersistenceServerServiceTest {
@Autowired
protected ApplicationConfigRepository applicationConfigRepository;
@Autowired
protected TenantsClient tenantsClient;
@Autowired
protected TenantRepository tenantRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@Before
public void setupOptimize() {
createDefaultTenant();
ApplicationConfig appConfig = new ApplicationConfig().builder()
.downloadCleanupDownloadFilesHours(8).downloadCleanupNotDownloadFilesHours(72).softDeleteCleanupTime(96).build();
.downloadCleanupDownloadFilesHours(8)
.downloadCleanupNotDownloadFilesHours(72)
.softDeleteCleanupTime(96)
.build();
appConfigClient.createOrUpdateAppConfig(appConfig);
// when(appConfigClient.getCurrentApplicationConfig()).thenReturn(ApplicationConfig.builder()
@ -153,94 +215,144 @@ public abstract class AbstractPersistenceServerServiceTest {
DocumentRequest d = (DocumentRequest) args[0];
var untouchedObjectId = StorageIdUtils.getStorageId(d.getDossierId(), d.getFileId(), FileType.ORIGIN);
return new ByteContentDocument(IOUtils.toByteArray(storageService.getObject(untouchedObjectId).getInputStream()));
return new ByteContentDocument(IOUtils.toByteArray(storageService.getObject(untouchedObjectId)
.getInputStream()));
});
when(pdfTronRedactionClient.redactionPreview(Mockito.any())).thenAnswer((answer) -> {
Object[] args = answer.getArguments();
DocumentRequest d = (DocumentRequest) args[0];
var untouchedObjectId = StorageIdUtils.getStorageId(d.getDossierId(), d.getFileId(), FileType.ORIGIN);
return new ByteContentDocument(IOUtils.toByteArray(storageService.getObject(untouchedObjectId).getInputStream()));
return new ByteContentDocument(IOUtils.toByteArray(storageService.getObject(untouchedObjectId)
.getInputStream()));
});
when(pdfTronRedactionClient.redactionPreviewDiff(Mockito.any())).thenAnswer((answer) -> {
Object[] args = answer.getArguments();
DocumentRequest d = (DocumentRequest) args[0];
var untouchedObjectId = StorageIdUtils.getStorageId(d.getDossierId(), d.getFileId(), FileType.ORIGIN);
return new ByteContentDocument(IOUtils.toByteArray(storageService.getObject(untouchedObjectId).getInputStream()));
return new ByteContentDocument(IOUtils.toByteArray(storageService.getObject(untouchedObjectId)
.getInputStream()));
});
when(redactionClient.sections(Mockito.any())).thenReturn(new RedactionResult());
when(redactionClient.getRedactionLog(Mockito.any())).thenReturn(new RedactionLog(1, 1,
Lists.newArrayList(), null, 0, 0, 0, 0));
when(redactionClient.getRedactionLog(Mockito.any())).thenReturn(new RedactionLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0));
}
private void createDefaultTenant() {
if(!tenantRepository.findById("redaction").isPresent()) {
var postgreSQLContainerMaster = SpringPostgreSQLTestContainer.getInstance()
.withDatabaseName("integration-tests-db-master")
.withUsername("sa")
.withPassword("sa");
var jdbcUrl = postgreSQLContainerMaster.getJdbcUrl().substring(0, postgreSQLContainerMaster.getJdbcUrl().lastIndexOf('/') + 1) + "redaction?currentSchema=myschema";
createDatabase("redaction", "redaction");
createSchema(jdbcUrl, "redaction", "redaction");
tenantsClient.createTenant(new Tenant("redaction", jdbcUrl, "redaction", "redaction"));
}
}
private void createDatabase(String db, String password) {
jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("CREATE DATABASE " + db));
jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("CREATE USER " + db + " WITH ENCRYPTED PASSWORD '" + password + "'"));
jdbcTemplate.execute((StatementCallback<Boolean>) stmt -> stmt.execute("GRANT ALL PRIVILEGES ON DATABASE " + db + " TO " + db));
}
@SneakyThrows
public void createSchema(String jdbcUrl, String username, String password) {
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
DataSource tenantDataSource = new SingleConnectionDataSource(connection, false);
JdbcTemplate insert = new JdbcTemplate(tenantDataSource);
insert.execute((StatementCallback<Boolean>) stmt -> stmt.execute("CREATE SCHEMA myschema"));
insert.execute((StatementCallback<Boolean>) stmt -> stmt.execute("GRANT USAGE ON SCHEMA myschema TO " + username));
}
}
@After
public void cleanupStorage() {
((FileSystemBackedStorageService) this.storageService).clearStorage();
}
@After
public void printMetrics() {
this.metricsPrinterService.printMetrics();
}
@After
public void afterTests() {
falsePositiveEntryRepository.deleteAll();
falseRecommendationEntryRepository.deleteAll();
entryRepository.deleteAll();
dossierAttributeRepository.deleteAll();
dossierAttributeConfigRepository.deleteAll();
downloadStatusRepository.deleteAll();
fileAttributesRepository.deleteAll();
smtpRepository.deleteAll();
digitalSignatureRepository.deleteAll();
fileAttributesGeneralConfigurationRepository.deleteAll();
fileAttributeConfigRepository.deleteAll();
reportTemplateRepository.deleteAll();
typeRepository.deleteAll();
viewedPagesRepository.deleteAll();
notificationRepository.deleteAll();
auditRepository.deleteAll();
manualRedactionRepository.deleteAll();
forceRedactionRepository.deleteAll();
removeRedactionRepository.deleteAll();
legalBasisChangeRepository.deleteAll();
imageRecategorizationRepository.deleteAll();
legalBasisMappingRepository.deleteAll();
ruleSetRepository.deleteAll();
smtpRepository.deleteAll();
fileRepository.deleteAll();
dossierRepository.deleteAll();
dossierStatusRepository.deleteAll();
watermarkRepository.deleteAll();
dossierTemplateRepository.deleteAll();
notificationPreferencesRepository.deleteAll();
indexInformationRepository.deleteAll();
applicationConfigRepository.deleteAll();
tenantRepository.findAll().forEach(tenant -> {
TenantContext.setTenantId(tenant.getTenantId());
falsePositiveEntryRepository.deleteAll();
falseRecommendationEntryRepository.deleteAll();
entryRepository.deleteAll();
dossierAttributeRepository.deleteAll();
dossierAttributeConfigRepository.deleteAll();
downloadStatusRepository.deleteAll();
fileAttributesRepository.deleteAll();
smtpRepository.deleteAll();
digitalSignatureRepository.deleteAll();
fileAttributesGeneralConfigurationRepository.deleteAll();
fileAttributeConfigRepository.deleteAll();
reportTemplateRepository.deleteAll();
typeRepository.deleteAll();
viewedPagesRepository.deleteAll();
notificationRepository.deleteAll();
auditRepository.deleteAll();
manualRedactionRepository.deleteAll();
forceRedactionRepository.deleteAll();
removeRedactionRepository.deleteAll();
legalBasisChangeRepository.deleteAll();
imageRecategorizationRepository.deleteAll();
legalBasisMappingRepository.deleteAll();
ruleSetRepository.deleteAll();
smtpRepository.deleteAll();
fileRepository.deleteAll();
dossierRepository.deleteAll();
dossierStatusRepository.deleteAll();
watermarkRepository.deleteAll();
dossierTemplateRepository.deleteAll();
notificationPreferencesRepository.deleteAll();
indexInformationRepository.deleteAll();
applicationConfigRepository.deleteAll();
});
}
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
var postgreSQLContainer = SpringPostgreSQLTestContainer.getInstance()
.withDatabaseName("integration-tests-db")
var postgreSQLContainerMaster = SpringPostgreSQLTestContainer.getInstance()
.withDatabaseName("integration-tests-db-master")
.withUsername("sa")
.withPassword("sa");
postgreSQLContainer.start();
postgreSQLContainerMaster.start();
var connectionStringDetails = "?serverTimezone=UTC&cachePrepStmts=true&useServerPrepStmts=true&rewriteBatchedStatements=true";
TestPropertyValues.of(
"spring.datasource.url=" + postgreSQLContainer.getJdbcUrl() + connectionStringDetails,
"spring.datasource.username=" + postgreSQLContainer.getUsername(),
"spring.datasource.password=" + postgreSQLContainer.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
TestPropertyValues.of("multitenancy.master.datasource.url=" + postgreSQLContainerMaster.getJdbcUrl() + connectionStringDetails, "multitenancy.master.datasource.username=" + postgreSQLContainerMaster.getUsername(), "multitenancy.master.datasource.password=" + postgreSQLContainerMaster.getPassword())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
@Configuration
@ -251,10 +363,10 @@ public abstract class AbstractPersistenceServerServiceTest {
@Bean
@Primary
public StorageService inmemoryStorage() {
return new FileSystemBackedStorageService();
}
}
}

View File

@ -3,14 +3,6 @@ monitoring.enabled: true
spring:
main:
allow-circular-references: true # FIXME
datasource:
driverClassName: org.postgresql.Driver
platform: org.hibernate.dialect.PostgreSQL95Dialect
hikari:
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
jpa:
database-platform: org.hibernate.dialect.PostgreSQL95Dialect
hibernate:
@ -79,3 +71,30 @@ management:
endpoints.web.exposure.include: prometheus, health, metrics
logging.level.root: info
multitenancy:
datasource-cache:
maximumSize: 100
expireAfterAccess: 1
master:
datasource:
driverClassName: org.postgresql.Driver
platform: org.hibernate.dialect.PostgreSQL95Dialect
hikari:
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
liquibase:
changeLog: classpath:db/changelog/db.changelog-master.yaml
tenant:
datasource:
driverClassName: org.postgresql.Driver
platform: org.hibernate.dialect.PostgreSQL95Dialect
hikari:
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 1000
prepStmtCacheSqlLimit: 2048
liquibase:
changeLog: classpath:db/changelog/db.changelog-tenant.yaml