Pull request #674: RED-6467

Merge in RED/persistence-service from RED-6467 to master

* commit '0422a98e5e8f7cef0ba2edeb9d17ed03c613f540': (25 commits)
  RED-6467: Removed debugging code
  RED-6467: Removed debugging code
  RED-6467: Removed debugging code
  RED-6467: Corrected unique name check logic
  RED-6467: Changed unique name check to not use Exceptions to prevent transaction rollbacks
  RED-6467: Added more debugging output
  RED-6467: Added more debugging output
  RED-6467: Added more debugging output
  RED-6467: Remove debug config and add some debugging output
  RED-6467: Added config to see more information about transactions in the logs
  RED-6467: Expanded dossier-template import (integration) test to use the client, to test the complete code flow
  RED-6467: Upgrade test db version to the same version as in production
  RED-6467: Rearranged code so that query-objects are not re-used to see if it fixes transaction behavior
  RED-6467: Split query into a select and a update query to prevent transaction rollbacks
  RED-6467: Added missing transaction annotation
  RED-6467: Removed debugging logs
  RED-6467: Added missing lombok annotations
  RED-6467: Implemented undeletion of dictionary entries by running a native query in chunks.
  RED-6467: Implemented undeletion of dictionary entries via the criteria builder.
  RED-6467: Implemented undeletion as operation on entities
  ...
This commit is contained in:
Viktor Seifert 2023-04-21 15:24:59 +02:00
commit 3376f55655
26 changed files with 408 additions and 124 deletions

View File

@ -112,6 +112,7 @@ public class DossierTemplateImportService {
public String importDossierTemplate(ImportDossierTemplateRequest request) {
ImportTemplateResult archiveResult = this.handleArchive(request);
return this.importDossierTemplate(archiveResult);
}
@ -288,15 +289,19 @@ public class DossierTemplateImportService {
long start = System.currentTimeMillis();
String dossierTemplateId;
var dossierTemplateMeta = request.getDossierTemplate();
DossierTemplateEntity existingDossierTemplate = null;
if (request.getDossierTemplateId() != null && request.isUpdateExistingTemplate()) {
var dossierTemplateOptional = dossierTemplateRepository.findByIdAndNotDeleted(request.getDossierTemplateId());
existingDossierTemplate = dossierTemplateOptional.orElse(null);
}
if (existingDossierTemplate != null) {
dossierTemplateId = existingDossierTemplate.getId();
// override the existing dossier template
updateDossierTemplateMeta(existingDossierTemplate, dossierTemplateMeta, request.getUserId());
// set rules
rulesPersistenceService.setRules(request.getRuleSet(), dossierTemplateId);
@ -374,11 +379,12 @@ public class DossierTemplateImportService {
reportsUpdated.add(report.getTemplateId());
});
}
// delete the reports that were not in the import
existingReports.forEach(r -> {
String storageId = r.getStorageId();
if (!reportsUpdated.contains(r.getTemplateId())) {
storageService.deleteObject(TenantContext.getTenantId(),storageId);
storageService.deleteObject(TenantContext.getTenantId(), storageId);
reportTemplatePersistenceService.delete(r.getTemplateId());
}
});
@ -517,6 +523,7 @@ public class DossierTemplateImportService {
if (!dossierTemplateEntity.getName().equalsIgnoreCase(dossierTemplate.getName())) {
this.validateDossierTemplateName(dossierTemplate);
}
OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
dossierTemplateEntity.setName(dossierTemplate.getName());
dossierTemplateEntity.setDescription(dossierTemplate.getDescription());
@ -671,22 +678,16 @@ public class DossierTemplateImportService {
private void validateDossierTemplateName(DossierTemplate dossierTemplateMeta) {
boolean cond = true;
int index = 0;
int nameSuffix = 0;
String dossierTemplateName = dossierTemplateMeta.getName();
do {
try {
dossierTemplatePersistenceService.validateDossierTemplateNameIsUnique(dossierTemplateMeta.getName());
cond = false;
} catch (ConflictException e) {
if (index == 0) {
dossierTemplateMeta.setName("Copy of " + dossierTemplateName);
} else {
dossierTemplateMeta.setName("Copy of " + dossierTemplateName + " - " + index);
}
index++;
while (dossierTemplatePersistenceService.isDossierTemplateNameNotUnique(dossierTemplateMeta.getName())) {
if (nameSuffix == 0) {
dossierTemplateMeta.setName("Copy of " + dossierTemplateName);
} else {
dossierTemplateMeta.setName("Copy of " + dossierTemplateName + " - " + nameSuffix);
}
} while (cond);
nameSuffix++;
}
}

View File

@ -17,8 +17,8 @@ import com.iqser.red.service.persistence.management.v1.processor.exception.BadRe
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
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.DossierTemplateRepository;
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.TypeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionarySummaryResponse;
import lombok.RequiredArgsConstructor;

View File

@ -73,14 +73,20 @@ public class DossierTemplatePersistenceService {
}
@Transactional
public void validateDossierTemplateNameIsUnique(String templateName) {
getAllDossierTemplates().forEach(existing -> {
if (existing.getName().equals(templateName)) {
throw new ConflictException("DossierTemplate name must be unique");
}
});
if (isDossierTemplateNameNotUnique(templateName)) {
throw new ConflictException("DossierTemplate name must be unique");
}
}
@Transactional
public boolean isDossierTemplateNameNotUnique(String templateName) {
return dossierTemplateRepository.existsByName(templateName);
}

View File

@ -12,10 +12,10 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.configur
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryFalsePositiveEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryFalseRecommendationEntryEntity;
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.TypeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalsePositiveEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalseRecommendationEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.jdbc.JDBCWriteUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
@ -97,13 +97,16 @@ public class EntryPersistenceService {
}
}
@Transactional
public void addEntries(String typeId, Set<String> entries, long version, DictionaryEntryType dictionaryEntryType) {
var type = typeRepository.getById(typeId);
switch (dictionaryEntryType) {
case ENTRY: {
case ENTRY -> {
var undeletedEntries = entryRepository.undeleteEntries(typeId, entries, version);
undeletedEntries.forEach(entries::remove);
var entryEntities = entries.stream().map(e -> {
@ -116,9 +119,8 @@ public class EntryPersistenceService {
}).collect(Collectors.toList());
jdbcWriteUtils.saveBatch(entryEntities);
break;
}
case FALSE_POSITIVE: {
case FALSE_POSITIVE -> {
var undeletedEntries = falsePositiveEntryRepository.undeleteEntries(typeId, entries, version);
undeletedEntries.forEach(entries::remove);
@ -131,9 +133,8 @@ public class EntryPersistenceService {
}).collect(Collectors.toList());
jdbcWriteUtils.saveBatch(entryEntities);
break;
}
case FALSE_RECOMMENDATION: {
case FALSE_RECOMMENDATION -> {
var undeletedEntries = falseRecommendationEntryRepository.undeleteEntries(typeId, entries, version);
undeletedEntries.forEach(entries::remove);
@ -146,7 +147,6 @@ public class EntryPersistenceService {
}).collect(Collectors.toList());
jdbcWriteUtils.saveBatch(entryEntities);
break;
}
}
}

View File

@ -17,4 +17,7 @@ public interface DossierTemplateRepository extends JpaRepository<DossierTemplate
@Query("select d from DossierTemplateEntity d where d.id = :dossierTemplateId and d.softDeleteTime is null")
Optional<DossierTemplateEntity> findByIdAndNotDeleted(String dossierTemplateId);
boolean existsByName(String name);
}

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository;
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
import javax.transaction.Transactional;
@ -11,7 +10,7 @@ import org.springframework.data.jpa.repository.Query;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryEntryEntity;
public interface EntryRepository extends JpaRepository<DictionaryEntryEntity, Long> {
public interface EntryRepository extends EntryRepositoryCustom, JpaRepository<DictionaryEntryEntity, Long> {
@Modifying
@Query("update DictionaryEntryEntity e set e.deleted = true, e.version = :version where e.typeId = :typeId and e.value in :values")
@ -36,13 +35,6 @@ public interface EntryRepository extends JpaRepository<DictionaryEntryEntity, Lo
@Query("update DictionaryEntryEntity e set e.deleted = true, e.version = :version where e.typeId = :typeId")
void deleteAllEntriesForTypeId(String typeId, long version);
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Transactional
@Query(value = "update dictionary_entry set deleted = false, version = :version where type_id = :typeId and value in (:entries) returning value", nativeQuery = true)
List<String> undeleteEntries(String typeId, Set<String> entries, long version);
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Transactional
@Query(value = "insert into dictionary_entry (value, version, deleted, type_id) " + " select value, 1, false, :newTypeId from dictionary_entry where type_id = :originalTypeId and deleted = false", nativeQuery = true)

View File

@ -0,0 +1,10 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
public interface EntryRepositoryCustom {
List<String> undeleteEntries(String typeId, Set<String> entries, long version);
}

View File

@ -0,0 +1,26 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
import org.springframework.stereotype.Repository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@Repository
public class EntryRepositoryImpl implements EntryRepositoryCustom {
QueryExecutor queryExecutor;
@Override
public List<String> undeleteEntries(String typeId, Set<String> entries, long version) {
return queryExecutor.runUndeleteQueryInBatches(typeId, entries, version, "dictionary_entry");
}
}

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository;
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
import javax.transaction.Transactional;
@ -11,7 +10,7 @@ import org.springframework.data.jpa.repository.Query;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryFalsePositiveEntryEntity;
public interface FalsePositiveEntryRepository extends JpaRepository<DictionaryFalsePositiveEntryEntity, Long> {
public interface FalsePositiveEntryRepository extends FalsePositiveEntryRepositoryCustom, JpaRepository<DictionaryFalsePositiveEntryEntity, Long> {
@Modifying
@Query("update DictionaryFalsePositiveEntryEntity e set e.deleted = true , e.version = :version where e.typeId = :typeId and e.value in :values")
@ -31,13 +30,6 @@ public interface FalsePositiveEntryRepository extends JpaRepository<DictionaryFa
@Query("update DictionaryFalsePositiveEntryEntity e set e.deleted = true, e.version = :version where e.typeId = :typeId")
void deleteAllEntriesForTypeId(String typeId, long version);
@Modifying
@Transactional
@Query(value = "update dictionary_false_positive_entry set deleted = false, version = :version where type_id = :typeId and value in (:entries) returning value", nativeQuery = true)
List<String> undeleteEntries(String typeId, Set<String> entries, long version);
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Transactional
@Query(value = "insert into dictionary_false_positive_entry (value, version, deleted, type_id) " + " select value, 1, false, :newTypeId from dictionary_false_positive_entry where type_id = :originalTypeId and deleted = false", nativeQuery = true)

View File

@ -0,0 +1,10 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
public interface FalsePositiveEntryRepositoryCustom {
List<String> undeleteEntries(String typeId, Set<String> entries, long version);
}

View File

@ -0,0 +1,26 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
import org.springframework.stereotype.Repository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@Repository
class FalsePositiveEntryRepositoryImpl implements FalsePositiveEntryRepositoryCustom {
QueryExecutor queryExecutor;
@Override
public List<String> undeleteEntries(String typeId, Set<String> entries, long version) {
return queryExecutor.runUndeleteQueryInBatches(typeId, entries, version, "dictionary_false_positive_entry");
}
}

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository;
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
import javax.transaction.Transactional;
@ -11,7 +10,7 @@ import org.springframework.data.jpa.repository.Query;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryFalseRecommendationEntryEntity;
public interface FalseRecommendationEntryRepository extends JpaRepository<DictionaryFalseRecommendationEntryEntity, Long> {
public interface FalseRecommendationEntryRepository extends FalseRecommendationEntryRepositoryCustom, JpaRepository<DictionaryFalseRecommendationEntryEntity, Long> {
@Modifying
@Query("update DictionaryFalseRecommendationEntryEntity e set e.deleted = true , e.version = :version where e.typeId = :typeId and e.value in :values")
@ -32,12 +31,6 @@ public interface FalseRecommendationEntryRepository extends JpaRepository<Dictio
void deleteAllEntriesForTypeId(String typeId, long version);
@Modifying
@Transactional
@Query(value = "update dictionary_false_recommendation_entry set deleted = false, version = :version where type_id = :typeId and value in (:entries) returning value", nativeQuery = true)
List<String> undeleteEntries(String typeId, Set<String> entries, long version);
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Transactional
@Query(value = "insert into dictionary_false_recommendation_entry (value, version, deleted, type_id) " + " select value, 1, false, :newTypeId from dictionary_false_recommendation_entry where type_id = :originalTypeId and deleted = false", nativeQuery = true)

View File

@ -0,0 +1,10 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
public interface FalseRecommendationEntryRepositoryCustom {
List<String> undeleteEntries(String typeId, Set<String> entries, long version);
}

View File

@ -0,0 +1,26 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.List;
import java.util.Set;
import org.springframework.stereotype.Repository;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@Repository
class FalseRecommendationEntryRepositoryImpl implements FalseRecommendationEntryRepositoryCustom {
QueryExecutor queryExecutor;
@Override
public List<String> undeleteEntries(String typeId, Set<String> entries, long version) {
return queryExecutor.runUndeleteQueryInBatches(typeId, entries, version, "dictionary_false_recommendation_entry");
}
}

View File

@ -0,0 +1,109 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.transaction.Transactional;
import org.springframework.stereotype.Component;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@RequiredArgsConstructor
@Component
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
class QueryExecutor {
private static final String FETCH_ENTRY_VALUES_QUERY = """
select value from ::tableName::
where type_id = :typeId and value in (:entries)""";
private static final String UPDATE_ENTRIES_QUERY = """
update ::tableName::
set deleted = false, version = :version
where type_id = :typeId and value in (:entries)""";
// Currently (2023-04-13) there is a limitation in the Postgres JDBC driver, that limits the number of elements in a "IN" clause
// to the max value of a 'short'. We subtract a small value to be on the safe side, since it is unclear what contributes
// to the number of elements, only the elements or parentheses etc.
private static final int ELEMENT_CHUNK_SIZE = Short.MAX_VALUE - 10;
EntityManager entityManager;
@Transactional
public LinkedList<String> runUndeleteQueryInBatches(String typeId, Set<String> entries, long version, String tableName) {
var results = new LinkedList<String>();
var entryList = new ArrayList<>(entries);
for (int fromIndex = 0, toIndex = ELEMENT_CHUNK_SIZE; ; ) {
toIndex = Math.min(toIndex, entryList.size());
if (fromIndex >= entryList.size()) {
break;
}
var values = entryList.subList(fromIndex, toIndex);
var entryValues = executeFetchValuesQuery(typeId, tableName, values);
results.addAll(entryValues);
executeUpdateQuery(typeId, version, tableName, values);
fromIndex += ELEMENT_CHUNK_SIZE;
toIndex += ELEMENT_CHUNK_SIZE;
}
return results;
}
private void executeUpdateQuery(String typeId, long version, String tableName, List<String> values) {
String updateSql = getUpdateEntriesQuery(tableName);
Query updateEntriesQuery = entityManager.createNativeQuery(updateSql);
updateEntriesQuery.setParameter("typeId", typeId);
updateEntriesQuery.setParameter("version", version);
updateEntriesQuery.setParameter("entries", values);
updateEntriesQuery.executeUpdate();
}
// The call to query.getResultList returns an untyped list, there is no way around that.
// So we suppress the warning.
// CAUTION: Make sure that the query actually returns a list of Strings.
@SuppressWarnings("unchecked")
private List<String> executeFetchValuesQuery(String typeId, String tableName, List<String> values) {
String fetchSql = getFetchEntryValuesQuery(tableName);
Query fetchEntryValuesQuery = entityManager.createNativeQuery(fetchSql);
fetchEntryValuesQuery.setParameter("typeId", typeId);
fetchEntryValuesQuery.setParameter("entries", values);
return fetchEntryValuesQuery.getResultList();
}
private String getFetchEntryValuesQuery(String tableName) {
return FETCH_ENTRY_VALUES_QUERY.replace("::tableName::", tableName);
}
private String getUpdateEntriesQuery(String tableName) {
return UPDATE_ENTRIES_QUERY.replace("::tableName::", tableName);
}
}

View File

@ -166,7 +166,7 @@ springdoc:
path: /redaction-gateway-v1/docs?tenantId=redaction
enabled: ${swagger.enabled}
pre-loading-enabled: true
packages-to-scan: ['com.iqser.red.persistence.service.v1.external.api']
packages-to-scan: [ 'com.iqser.red.persistence.service.v1.external.api' ]
swagger:
enabled: true

View File

@ -1,8 +1,11 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -13,10 +16,15 @@ import java.util.zip.ZipOutputStream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.web.MockMultipartFile;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateImportService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateManagementService;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplate;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
import lombok.SneakyThrows;
@ -25,8 +33,12 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DossierTemplateImportTest extends AbstractPersistenceServerServiceTest {
public static final String DOSSIERTEMPLATES_PATH = "files/dossiertemplates/";
@Autowired
private DossierTemplateImportService dossierTemplateImportService;
private DossierTemplateManagementService dossierTemplateManagementService;
@Autowired
private DossierTemplateClient dossierTemplateClient;
@SneakyThrows
@ -35,17 +47,21 @@ public class DossierTemplateImportTest extends AbstractPersistenceServerServiceT
public void testDossierTemplateImport() {
var importDir = new File("/Users/timobejan/work/dossier-templates-v2/dev");
assertThat(importDir).isNotNull();
assertThat(importDir.exists()).isTrue();
TenantContext.setTenantId("redaction");
for (var file : importDir.listFiles()) {
if(file.isDirectory()){
if (file.isDirectory()) {
var archive = pack(file.getAbsolutePath());
log.info("Importing file: " + file.getName() + " " + " with size: " + archive.length);
var request = new ImportDossierTemplateRequest();
request.setArchive(archive);
request.setUpdateExistingDossierTemplate(false);
request.setUserId("system");
dossierTemplateImportService.importDossierTemplate(request);
DossierTemplate dossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);
assertThat(dossierTemplate).isNotNull();
assertThat(dossierTemplate.getId()).isNotBlank();
}
}
}
@ -56,18 +72,19 @@ public class DossierTemplateImportTest extends AbstractPersistenceServerServiceT
var bos = new ByteArrayOutputStream();
var p = Paths.get(sourceDirPath);
try (ZipOutputStream zs = new ZipOutputStream(bos)) {
Stream<Path> paths = Files.walk(p);
{
paths.filter(path -> !Files.isDirectory(path)).forEach(path -> {
ZipEntry zipEntry = new ZipEntry(p.relativize(path).toString());
try {
zs.putNextEntry(zipEntry);
Files.copy(path, zs);
zs.closeEntry();
} catch (IOException e) {
System.err.println(e);
}
});
try (Stream<Path> paths = Files.walk(p)) {
{
paths.filter(path -> !Files.isDirectory(path)).forEach(path -> {
ZipEntry zipEntry = new ZipEntry(p.relativize(path).toString());
try {
zs.putNextEntry(zipEntry);
Files.copy(path, zs);
zs.closeEntry();
} catch (IOException e) {
log.error("Failed to read a file", e);
}
});
}
}
}
@ -75,6 +92,42 @@ public class DossierTemplateImportTest extends AbstractPersistenceServerServiceT
}
@Test
@SneakyThrows
@Disabled
public void dossierImportClientTest() {
var multipartFile = loadDossierTemplateFromClasspath("EFSA_sanitisation_GFL_v1.zip");
DossierTemplateModel dossierTemplateModel = dossierTemplateClient.importDossierTemplate(multipartFile, null, false);
assertThat(dossierTemplateModel).isNotNull();
assertThat(dossierTemplateModel.getId()).isNotBlank();
}
@SneakyThrows
private MockMultipartFile loadDossierTemplateFromClasspath(String filename) {
ClassPathResource classPathResource = new ClassPathResource(DOSSIERTEMPLATES_PATH + filename);
assertThat(classPathResource.exists()).isTrue();
try (InputStream inputStream = classPathResource.getInputStream()) {
return new MockMultipartFile(filename, filename, "", inputStream);
}
}
// This can be used to test with local files that are not part of the project.
@SuppressWarnings("unused")
@SneakyThrows
private MockMultipartFile loadDossierTemplateFromFile(String filename) {
File file = new File(filename);
assertThat(file.exists()).isTrue();
return new MockMultipartFile(filename, filename, "", Files.readAllBytes(file.toPath()));
}
}

View File

@ -8,7 +8,6 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -37,10 +36,8 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.PageExclusi
import com.iqser.red.service.persistence.service.v1.api.shared.model.PageRange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateDictionaryStats;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DownloadFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionarySummary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTest {
@ -103,7 +100,7 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
.description("Something")
.addToDictionaryAction(false)
.dossierTemplateId(dossier.getDossierTemplateId())
.build(),null);
.build(), null);
assertThat(addedType1).isNotNull();
@ -118,13 +115,13 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
.addToDictionaryAction(false)
.dossierTemplateId(dossierTemplate2.getId())
.hasDictionary(true)
.build(),null);
.build(), null);
assertThat(addedType).isNotNull();
var entries1 = new ArrayList<String>();
entries1.add("entry1");
entries1.add("entry2");
dictionaryClient.addEntry(addedType.getType(),addedType.getDossierTemplateId(), entries1, false, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(addedType.getType(), addedType.getDossierTemplateId(), entries1, false, null, DictionaryEntryType.ENTRY);
var dictionary = dictionaryClient.getDictionaryForType(addedType.getType(), addedType.getDossierTemplateId(), null);
assertThat(dictionary.getEntries().size()).isEqualTo(entries1.size());
@ -138,13 +135,12 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
.addToDictionaryAction(false)
.dossierTemplateId(dossierTemplate2.getId())
.hasDictionary(true)
.build(),null);
.build(), null);
var entries2 = new ArrayList<String>();
entries2.add("entry1");
entries2.add("entry2");
entries2.add("entry3");
dictionaryClient.addEntry(addedType2.getType(),addedType2.getDossierTemplateId(), entries2, false, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(addedType2.getType(), addedType2.getDossierTemplateId(), entries2, false, null, DictionaryEntryType.ENTRY);
dictionary = dictionaryClient.getDictionaryForType(addedType2.getType(), addedType.getDossierTemplateId(), null);
assertThat(dictionary.getEntries().size()).isEqualTo(entries2.size());
@ -159,13 +155,13 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
.addToDictionaryAction(false)
.dossierTemplateId(dossierTemplate3.getId())
.hasDictionary(true)
.build(),null);
.build(), null);
assertThat(addedType3).isNotNull();
var entries3 = new ArrayList<String>();
entries3.add("entry1");
entries3.add("entry2");
dictionaryClient.addEntry(addedType3.getType(),addedType3.getDossierTemplateId(), entries3, false, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(addedType3.getType(), addedType3.getDossierTemplateId(), entries3, false, null, DictionaryEntryType.ENTRY);
dictionary = dictionaryClient.getDictionaryForType(addedType3.getType(), addedType3.getDossierTemplateId(), null);
assertThat(dictionary.getEntries().size()).isEqualTo(entries3.size());
@ -183,7 +179,7 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
.addToDictionaryAction(false)
.dossierTemplateId(dossierTemplate5.getId())
.hasDictionary(true)
.build(),null);
.build(), null);
assertThat(addedType5).isNotNull();
Set<String> dossierTemplateIds = new HashSet<>();
@ -218,7 +214,7 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
var entries22 = new ArrayList<String>();
entries22.add(entries2.get(2));
dictionaryClient.deleteEntries(addedType2.getType(), addedType2.getDossierTemplateId(),entries22,null, DictionaryEntryType.ENTRY);
dictionaryClient.deleteEntries(addedType2.getType(), addedType2.getDossierTemplateId(), entries22, null, DictionaryEntryType.ENTRY);
dossierTemplateStatsList = dossierTemplateStatsClient.getDossierTemplateStats(dossierTemplateIds);
assertThat(dossierTemplateStatsList.size()).isEqualTo(dossierTemplateIds.size());
@ -235,7 +231,7 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
entries23.add(entries2.get(0));
entries23.add(entries2.get(1));
dictionaryClient.deleteEntries(addedType2.getType(),addedType2.getDossierTemplateId(), entries23, null,DictionaryEntryType.ENTRY);
dictionaryClient.deleteEntries(addedType2.getType(), addedType2.getDossierTemplateId(), entries23, null, DictionaryEntryType.ENTRY);
dictionary = dictionaryClient.getDictionaryForType(addedType2.getType(), addedType.getDossierTemplateId(), null);
var entries23loaded = dictionary.getEntries();
@ -277,7 +273,12 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
for (int i = 0; i < 2; i++) {
var template = dossierTemplateTesterAndProvider.provideTestTemplate("test template: " + i);
var status = dossierStatusClient.createOrUpdateDossierStatus(DossierStatusRequest.builder().dossierTemplateId(template.getId()).name("test").color("#cccccc").rank(100).build());
var status = dossierStatusClient.createOrUpdateDossierStatus(DossierStatusRequest.builder()
.dossierTemplateId(template.getId())
.name("test")
.color("#cccccc")
.rank(100)
.build());
for (int j = 0; j < 8; j++) {
var dossier = dossierTesterAndProvider.provideTestDossierQuick(template, "test dossier: " + j + " - " + i, j % 2 == 0 ? status : null);
@ -318,7 +319,12 @@ public class DossierTemplateStatsTest extends AbstractPersistenceServerServiceTe
var template = dossierTemplateTesterAndProvider.provideTestTemplate("test template inactive: ");
var status = dossierStatusClient.createOrUpdateDossierStatus(DossierStatusRequest.builder().dossierTemplateId(template.getId()).name("test").color("#cccccc").rank(100).build());
var status = dossierStatusClient.createOrUpdateDossierStatus(DossierStatusRequest.builder()
.dossierTemplateId(template.getId())
.name("test")
.color("#cccccc")
.rank(100)
.build());
var dossier = dossierTesterAndProvider.provideTestDossierQuick(template, "test dossier: ", status);
dossierClient.deleteDossier(dossier.getId());

View File

@ -7,6 +7,7 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -34,6 +35,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierAttr
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributesConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.WatermarkModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CloneDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierAttributeConfig;
@ -311,9 +313,9 @@ public class DossierTemplateTest extends AbstractPersistenceServerServiceTest {
assertThat(reportTemplateClient.getAvailableReportTemplates(dossierTemplate.getId()).get(0).getFileName()).isEqualTo(reportTemplateClient.getAvailableReportTemplates(
clonedDT.getId()).get(0).getFileName());
assertThat(dictionaryClient.getColors(dossierTemplate.getId()).getRequestAddColor()).isEqualTo(dictionaryClient.getColors(clonedDT.getId()).getRequestAddColor());
assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), null, false).getTypes().get(0).getRank()).isEqualTo(dictionaryClient.getAllTypes(clonedDT.getId(),
null,
false).getTypes().get(0).getRank());
var types = getTypesSortedByRank(dossierTemplate.getId());
var clonedTypes = getTypesSortedByRank(clonedDT.getId());
assertThat(types).usingElementComparatorIgnoringFields("typeId", "dossierTemplateId").isEqualTo(clonedTypes);
assertThat(dossierStatusClient.getAllDossierStatusForTemplate(dossierTemplate.getId())
.get(0)
.getDescription()).isEqualTo(dossierStatusClient.getAllDossierStatusForTemplate(clonedDT.getId()).get(0).getDescription());
@ -324,6 +326,19 @@ public class DossierTemplateTest extends AbstractPersistenceServerServiceTest {
}
@NotNull
private List<TypeValue> getTypesSortedByRank(String dossierTemplateId) {
return dictionaryClient.getAllTypes(dossierTemplateId, null, false).getTypes().stream().sorted(DossierTemplateTest::compareByRank).toList();
}
private static int compareByRank(TypeValue o1, TypeValue o2) {
return o1.getRank() - o2.getRank();
}
@SneakyThrows
@Test
public void testExportDossierTemplate() {
@ -663,15 +678,20 @@ public class DossierTemplateTest extends AbstractPersistenceServerServiceTest {
@SneakyThrows
public void testImportDossierTemplateNewTemplatewithCompressionRatioThresholdSurpassed() {
String fileZip = "EFSA_sanitisation_GFL_v1_adress_parts.zip";
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileZip);
String fileZip = "files/dossiertemplates/EFSA_sanitisation_GFL_v1_adress_parts.zip";
try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileZip)) {
if (inputStream == null) {
throw new RuntimeException("Resource " + fileZip + " was not found on the classpath or is not readable");
}
ImportDossierTemplateRequest request1 = ImportDossierTemplateRequest.builder()
.dossierTemplateId("sds")
.updateExistingDossierTemplate(false)
.userId("1")
.archive(inputStream.readAllBytes())
.build();
}
ImportDossierTemplateRequest request1 = ImportDossierTemplateRequest.builder()
.dossierTemplateId("sds")
.updateExistingDossierTemplate(false)
.userId("1")
.archive(inputStream.readAllBytes())
.build();
// TODO MIGRATION ***
// try {
// var newDossierTemplate = dossierTemplateClient.importDossierTemplate(request1);

View File

@ -15,7 +15,7 @@ import com.iqser.red.service.peristence.v1.server.integration.service.DossierTem
import com.iqser.red.service.peristence.v1.server.integration.service.TypeProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryEntryEntity;
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.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.jdbc.JDBCWriteUtils;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CloneDossierTemplateRequest;
@ -46,6 +46,7 @@ public class EntityPerformanceTest extends AbstractPersistenceServerServiceTest
@Test
public void testAddToDictionary() {
TenantContext.setTenantId("redaction");
var template = dossierTemplateTesterAndProvider.provideTestTemplate("test");
var type = typeProvider.testAndProvideType(template);
@ -54,29 +55,29 @@ public class EntityPerformanceTest extends AbstractPersistenceServerServiceTest
var tenKEntries = generateEntries(10000);
long t1 = System.currentTimeMillis();
dictionaryClient.addEntry(type.getType(),type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.ENTRY);
long t2 = System.currentTimeMillis();
log.info("Add Time: {}ms counting: {} entries", (t2 - t1), entryRepository.findByTypeIdAndVersionGreaterThan(type.getTypeId(), 0).size());
t1 = System.currentTimeMillis();
dictionaryClient.addEntry(type.getType(),type.getDossierTemplateId(), tenKEntries, true, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), tenKEntries, true, null, DictionaryEntryType.ENTRY);
t2 = System.currentTimeMillis();
log.info("Add Time: {}ms counting: {} entries", (t2 - t1), entryRepository.findByTypeIdAndVersionGreaterThan(type.getTypeId(), 0).size());
t1 = System.currentTimeMillis();
dictionaryClient.addEntry(type.getType(),type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.ENTRY);
t2 = System.currentTimeMillis();
log.info("Update Time: {}ms counting: {} entries", (t2 - t1), entryRepository.findByTypeIdAndVersionGreaterThan(type.getTypeId(), 0).size());
dictionaryClient.addEntry(type.getType(),type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.FALSE_RECOMMENDATION);
dictionaryClient.addEntry(type.getType(),type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.FALSE_POSITIVE);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.FALSE_RECOMMENDATION);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), fiveKEntries, true, null, DictionaryEntryType.FALSE_POSITIVE);
t1 = System.currentTimeMillis();
var cloned = dossierTemplateClient.cloneDossierTemplate(template.getId(), new CloneDossierTemplateRequest());
t2 = System.currentTimeMillis();
log.info("Clone Time: {}", (t2 - t1));
var types = dictionaryClient.getAllTypes(cloned.getId(), null,false).getTypes();
var types = dictionaryClient.getAllTypes(cloned.getId(), null, false).getTypes();
assertThat(types.size()).isEqualTo(1);
for (var clonedType : types) {

View File

@ -19,7 +19,6 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierStatsClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.client.WatermarkClient;
import com.iqser.red.service.peristence.v1.server.integration.service.UserProvider;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryEntryEntity;
@ -32,21 +31,18 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
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.DossierTemplateRepository;
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.FileAttributeConfigRepository;
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.TypeRepository;
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.shared.model.WatermarkModel;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DownloadFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.WatermarkOrientation;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileAttributeType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import liquibase.pro.packaged.A;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ -89,8 +85,7 @@ public class PerformanceTestService {
@Transactional
public void doSetup(){
public void doSetup() {
long start = System.currentTimeMillis();
@ -170,8 +165,8 @@ public class PerformanceTestService {
log.info("Created Dossier Template Data ...");
var user = userProvider.getUserId();
SecurityContextHolder.setContext(new TransientSecurityContext(new TestingAuthenticationToken(user,"secret")));
var user = userProvider.getUserId();
SecurityContextHolder.setContext(new TransientSecurityContext(new TestingAuthenticationToken(user, "secret")));
for (int i = 0; i < DOSSIER_COUNT; i++) {
@ -247,7 +242,6 @@ public class PerformanceTestService {
}
private Set<Integer> getExcludedPages() {
Set<Integer> pages = new HashSet<>();

View File

@ -63,9 +63,6 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
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;
@ -85,6 +82,9 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.LegalBasisChangeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.ManualRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.RemoveRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalsePositiveEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalseRecommendationEntryRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.redactionlog.RedactionLogMergeService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.multitenancy.TenantContext;

View File

@ -4,7 +4,7 @@ import org.testcontainers.containers.PostgreSQLContainer;
public class SpringPostgreSQLTestContainer extends PostgreSQLContainer<SpringPostgreSQLTestContainer> {
private static final String IMAGE_VERSION = "postgres:14.3";
private static final String IMAGE_VERSION = "postgres:15.2";
private static SpringPostgreSQLTestContainer container;

View File

@ -134,3 +134,9 @@ commons:
client-id: redaction
client-secret: redaction
feign:
client:
config:
DossierTemplateClient:
connectTimeout: 10000
readTimeout: 200000