RED-8702: Explore document databases to store entityLog

This commit is contained in:
Maverick Studer 2024-04-03 13:33:27 +02:00
parent f0c6b868e8
commit 8e8aa645d0
58 changed files with 304043 additions and 182 deletions

View File

@ -49,6 +49,7 @@ tasks.named<Test>("test") {
tasks.test {
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {

View File

@ -2,6 +2,7 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_REDACTION_LOG;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
@ -10,10 +11,11 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.mapper.EntityLogResponseMapper;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.EntityLogResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.FilteredEntityLogRequest;
import lombok.RequiredArgsConstructor;
@ -24,28 +26,51 @@ public class EntityLogController implements EntityLogResource {
private final EntityLogService entityLogService;
private final AccessControlService accessControlService;
private final EntityLogResponseMapper entityLogResponseMapper = EntityLogResponseMapper.INSTANCE;
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLog getEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedType", required = false) List<String> excludedTypes,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
public EntityLogResponse getEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedTypes", required = false) List<String> excludedTypes,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogService.getEntityLog(dossierId, fileId, excludedTypes, includeUnprocessed);
return entityLogResponseMapper.toLogResponse(entityLogService.getEntityLog(dossierId, fileId, excludedTypes == null ? new ArrayList<>() : excludedTypes, includeUnprocessed));
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLog getFilteredEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredEntityLogRequest filteredEntityLogRequest) {
public EntityLogResponse getFilteredEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredEntityLogRequest filteredEntityLogRequest) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogService.getFilteredEntityLog(dossierId, fileId, filteredEntityLogRequest);
return entityLogResponseMapper.toLogResponse(entityLogService.getFilteredEntityLog(dossierId, fileId, filteredEntityLogRequest));
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLogResponse getEntityLogWithEntriesOnPages(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "pageNumbers") List<Integer> pageNumbers) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogResponseMapper.toLogResponse(entityLogService.getEntityLogWithEntriesOnPages(dossierId, fileId, pageNumbers));
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public EntityLogResponse getEntityLogWithEntriesAnalysedAfterIteration(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANALYSIS_NUMBER) Integer analysisNumber) {
accessControlService.checkViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return entityLogResponseMapper.toLogResponse(entityLogService.getEntityLogWithEntriesWithEntriesByAnalysisNumber(dossierId, fileId, analysisNumber));
}
}

View File

@ -6,12 +6,11 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.FilteredEntityLogRequest;
import io.swagger.v3.oas.annotations.Operation;
@ -28,6 +27,8 @@ public interface EntityLogResource {
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String ANALYSIS_NUMBER = "analysisNumber";
String ANALYSIS_NUMBER_PATH_VARIABLE = "/{" + ANALYSIS_NUMBER + "}";
String FALSE = "false";
@ -37,17 +38,31 @@ public interface EntityLogResource {
"Gets the entity log for a given file. The flag includeUnprocessed will merge into the entity log all the unprocessed changes if it's set to true."
+ "Default value for the flag is false.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLog getEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedType", required = false) List<String> excludedTypes,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
@PostMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + "/filtered", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId grater than the specified date", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLog getFilteredEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
EntityLogResponse getEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredEntityLogRequest filteredEntityLogRequest);
@RequestParam(value = "excludedTypes", required = false) List<String> excludedTypes,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
@GetMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + "/filtered", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId greater than the specified date", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLogResponse getFilteredEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredEntityLogRequest filteredEntityLogRequest);
@GetMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + "/pages", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId with all entities found on the given page numbers", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLogResponse getEntityLogWithEntriesOnPages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestParam(value = "pageNumbers") List<Integer> pageNumbers);
@GetMapping(value = ENTITY_LOG_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE + ANALYSIS_NUMBER_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets the entity log for a fileId with all entities being analysed in or after iteration with given number", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "The dossier / file / entity log is not found.")})
EntityLogResponse getEntityLogWithEntriesAnalysedAfterIteration(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANALYSIS_NUMBER) Integer analysisNumber);
}

View File

@ -36,7 +36,7 @@ public class AdminInterfaceController {
public void resetFile(@RequestParam("dossierId") String dossierId, @RequestParam("fileId") String fileId) {
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.REDACTION_LOG);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.ENTITY_LOG);
fileManagementStorageService.deleteEntityLog(dossierId, fileId);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_PAGES);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_TEXT);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_POSITION);
@ -141,7 +141,7 @@ public class AdminInterfaceController {
var fileId = file.getId();
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.REDACTION_LOG);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.ENTITY_LOG);
fileManagementStorageService.deleteEntityLog(dossierId, fileId);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_STRUCTURE);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_TEXT);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_PAGES);

View File

@ -4,9 +4,11 @@ plugins {
}
val springBootStarterVersion = "3.1.5"
val springCloudVersion = "4.0.4"
dependencies {
api(project(":persistence-service-shared-api-v1"))
api(project(":persistence-service-shared-mongo-v1"))
api(project(":persistence-service-external-api-v1"))
api(project(":persistence-service-internal-api-v1"))
api("com.iqser.red.service:pdftron-redaction-service-api-v1:${rootProject.extra.get("pdftronRedactionServiceVersion")}") {
@ -34,8 +36,8 @@ dependencies {
exclude(group = "com.iqser.red.service", module = "persistence-service-shared-api-v1")
}
api("com.knecon.fforesight:jobs-commons:0.10.0")
api("com.knecon.fforesight:database-tenant-commons:0.21.0")
api("com.knecon.fforesight:keycloak-commons:0.25.0")
api("com.knecon.fforesight:database-tenant-commons:0.23.0")
api("com.knecon.fforesight:keycloak-commons:0.27.0")
api("com.knecon.fforesight:tracing-commons:0.5.0")
api("com.knecon.fforesight:swagger-commons:0.7.0")
api("com.giffing.bucket4j.spring.boot.starter:bucket4j-spring-boot-starter:0.4.0")
@ -55,8 +57,12 @@ dependencies {
api("org.postgresql:postgresql:42.2.23")
api("org.apache.commons:commons-lang3:3.12.0")
api("com.opencsv:opencsv:5.4")
api("org.springframework.cloud:spring-cloud-starter-openfeign:4.0.4")
api("org.springframework.cloud:spring-cloud-starter-openfeign:${springCloudVersion}")
api("commons-validator:commons-validator:1.7")
implementation("org.mapstruct:mapstruct:1.5.5.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.5.Final")
testImplementation("org.springframework.amqp:spring-rabbit-test:3.0.2")
testImplementation("org.testcontainers:postgresql:1.17.1")
testImplementation("org.springframework.boot:spring-boot-starter-test:3.0.4")

View File

@ -2,9 +2,8 @@ package com.iqser.red.service.persistence.management.v1.processor;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.support.PageJacksonModule;
import org.springframework.cloud.openfeign.support.SortJacksonModule;
@ -13,7 +12,6 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
@ -25,14 +23,15 @@ import com.iqser.red.service.persistence.management.v1.processor.client.redactio
import com.iqser.red.service.persistence.management.v1.processor.client.searchservice.SearchClient;
import com.iqser.red.service.persistence.management.v1.processor.client.tenantusermanagementservice.UsersClient;
import com.iqser.red.service.persistence.management.v1.processor.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.SharedMongoAutoConfiguration;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@ComponentScan
@EnableFeignClients(basePackageClasses = {PDFTronClient.class, StatusReportClient.class, SearchClient.class, RedactionClient.class, UsersClient.class})
@ImportAutoConfiguration(SharedMongoAutoConfiguration.class)
public class PersistenceServiceProcessorConfiguration {
public static final String TENANT_DATA_SOURCE_QUALIFIER = "multiTenantDataSource";

View File

@ -0,0 +1,36 @@
package com.iqser.red.service.persistence.management.v1.processor.mapper;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntryResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogResponse;
@Mapper
public interface EntityLogResponseMapper {
EntityLogResponseMapper INSTANCE = Mappers.getMapper(EntityLogResponseMapper.class);
EntityLog fromLogResponse(EntityLogResponse entityLogResponse);
EntityLogEntry fromLogEntryResponse(EntityLogEntryResponse entityLogEntryResponse);
List<EntityLogEntry> fromLogEntryResponses(List<EntityLogEntryResponse> entityLogEntryResponses);
EntityLogResponse toLogResponse(EntityLog entityLog);
EntityLogEntryResponse toLogEntryResponse(EntityLogEntry entityLogEntry);
List<EntityLogEntryResponse> toLogEntryResponses(List<EntityLogEntry> entityLogEntries);
}

View File

@ -1,6 +1,7 @@
package com.iqser.red.service.persistence.management.v1.processor.migration;
import static com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration.MIGRATION_QUEUE;
import static com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType.ENTITY_LOG;
import static com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingQueueNames.LAYOUT_PARSING_REQUEST_QUEUE;
import java.util.List;
@ -162,7 +163,7 @@ public class SaasMigrationService implements TenantSyncService {
saasMigrationStatusPersistenceService.updateStatus(fileId, SaasMigrationStatus.DOCUMENT_FILES_MIGRATED);
if(fileStatusPersistenceService.getStatus(fileId).getWorkflowStatus().equals(WorkflowStatus.APPROVED)) {
if (fileStatusPersistenceService.getStatus(fileId).getWorkflowStatus().equals(WorkflowStatus.APPROVED)) {
manualRedactionProviderService.convertUnprocessedAddToDictionariesToLocalChanges(fileId);
}
@ -210,9 +211,8 @@ public class SaasMigrationService implements TenantSyncService {
private boolean entityLogMigrationFilesExist(String dossierId, String fileId) {
return storageService.objectExists(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.ENTITY_LOG)) && storageService.objectExists(
TenantContext.getTenantId(),
StorageIdUtils.getStorageId(dossierId, fileId, FileType.MIGRATED_IDS));
return storageService.objectExists(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, ENTITY_LOG))
&& storageService.objectExists(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.MIGRATED_IDS));
}
@ -257,8 +257,6 @@ public class SaasMigrationService implements TenantSyncService {
}
private int addManualRedactionEntries(List<ManualRedactionEntry> manualRedactionEntriesToAdd) {
return manualRedactionService.addManualRedactionEntries(manualRedactionEntriesToAdd, true);

View File

@ -0,0 +1,80 @@
package com.iqser.red.service.persistence.management.v1.processor.migration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
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.FileRepository;
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.storage.commons.exception.StorageException;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class StorageToMongoCopyService {
private final FileManagementStorageService fileManagementStorageService;
private final StorageService storageService;
private final DossierRepository dossierRepository;
private final FileRepository fileRepository;
@SneakyThrows
public void copy() {
List<DossierEntity> dossierEntities = dossierRepository.findAll();
List<DossierFile> files = new ArrayList<>();
dossierEntities.forEach(dossierEntity -> {
List<FileEntity> byDossierId = fileRepository.findByDossierId(dossierEntity.getId());
byDossierId.forEach(file -> files.add(new DossierFile(dossierEntity.getId(), file.getId())));
});
for (DossierFile dossierFile : files) {
log.info("Reading dossier {} file {} from storage", dossierFile.dossierId, dossierFile.fileId);
Optional<EntityLog> entityLogFromStorage = getEntityLogFromStorageForMigration(dossierFile.dossierId, dossierFile.fileId);
if (entityLogFromStorage.isPresent()) {
log.info("File found, now saving in mongodb");
fileManagementStorageService.saveEntityLog(dossierFile.dossierId, dossierFile.fileId, entityLogFromStorage.get());
log.info("Deleting old file from storage");
fileManagementStorageService.deleteObject(dossierFile.dossierId, dossierFile.fileId, FileType.ENTITY_LOG);
}
}
}
private Optional<EntityLog> getEntityLogFromStorageForMigration(String dossierId, String fileId) {
try {
return Optional.ofNullable(storageService.readJSONObject(TenantContext.getTenantId(),
StorageIdUtils.getStorageId(dossierId, fileId, FileType.ENTITY_LOG),
EntityLog.class));
} catch (StorageObjectDoesNotExist e) {
log.debug("EntityLog does not exist");
} catch (StorageException e) {
log.debug(e.getMessage());
}
return Optional.empty();
}
public record DossierFile(String dossierId, String fileId) {
}
}

View File

@ -69,7 +69,7 @@ public class ManualRedactionTypeRenameMigration15 extends Migration {
continue;
}
if (!fileManagementStorageService.objectExists(file.getDossierId(), file.getId(), FileType.ENTITY_LOG)) {
if (!fileManagementStorageService.entityLogExists(file.getDossierId(), file.getId())) {
continue;
}

View File

@ -0,0 +1,37 @@
package com.iqser.red.service.persistence.management.v1.processor.migration.migrations;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.migration.Migration;
import com.iqser.red.service.persistence.management.v1.processor.migration.StorageToMongoCopyService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Setter
@Service
public class StorageToMongoMigration17 extends Migration {
private final StorageToMongoCopyService storageToMongoCopyService;
private static final String NAME = "Migration for entity log storage from s3 to mongodb";
private static final long VERSION = 17;
public StorageToMongoMigration17(StorageToMongoCopyService storageToMongoCopyService) {
super(NAME, VERSION);
this.storageToMongoCopyService = storageToMongoCopyService;
}
@Override
protected void migrate() {
log.info("Migration: Copying all files for all dossiers to mongodb");
storageToMongoCopyService.copy();
}
}

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
@ -47,7 +46,7 @@ public class AnalysisFlagsCalculationService {
long startTime = System.currentTimeMillis();
var file = fileStatusPersistenceService.getStatus(fileId);
var entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), true);
var entityLog = entityLogService.getEntityLog(dossierId, fileId, true);
var viewedPagesForCurrentAssignee = viewedPagesPersistenceService.findViewedPages(fileId, file.getAssignee());

View File

@ -2,7 +2,6 @@ package com.iqser.red.service.persistence.management.v1.processor.service;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -41,35 +40,45 @@ public class EntityLogService {
@Observed(name = "EntityLogService", contextualName = "get-entity-log")
public EntityLog getEntityLog(String dossierId, String fileId) {
return getEntityLog(dossierId, fileId, Collections.emptyList(), false);
EntityLog entityLog = fileManagementStorageService.getEntityLog(dossierId, fileId);
postProcessEntityLog(dossierId, fileId, entityLog, false);
return entityLog;
}
@Observed(name = "EntityLogService", contextualName = "get-entity-log")
public EntityLog getEntityLog(String dossierId, String fileId, boolean includeUnProcessed) {
EntityLog entityLog = fileManagementStorageService.getEntityLog(dossierId, fileId);
postProcessEntityLog(dossierId, fileId, entityLog, includeUnProcessed);
return entityLog;
}
@Observed(name = "EntityLogService", contextualName = "get-entity-log")
public EntityLog getEntityLog(String dossierId, String fileId, List<String> excludedTypes, boolean includeUnProcessed) {
EntityLog entityLog = fileManagementStorageService.getEntityLogExcludeTypes(dossierId, fileId, excludedTypes);
postProcessEntityLog(dossierId, fileId, entityLog, includeUnProcessed);
return entityLog;
}
@Observed(name = "EntityLogService", contextualName = "get-entity-log")
public EntityLog getEntityLog(String dossierId, String fileId, List<String> excludedTypes, boolean includeUnprocessed) {
@Observed(name = "EntityLogService", contextualName = "post-process-entity-log")
private void postProcessEntityLog(String dossierId, String fileId, EntityLog entityLog, boolean includeUnprocessed) {
EntityLog processedEntityLog = entityLog;
var fileStatus = fileStatusService.getStatus(fileId);
EntityLog entityLog;
entityLog = fileManagementStorageService.getEntityLog(dossierId, fileId);
if (fileStatus.isExcluded()) {
entityLog.setEntityLogEntry(new ArrayList<>());
}
if (excludedTypes != null) {
entityLog.getEntityLogEntry().removeIf(entry -> excludedTypes.contains(entry.getType()));
processedEntityLog.setEntityLogEntry(new ArrayList<>());
}
if (includeUnprocessed) {
DossierEntity dossier = dossierService.getDossierById(dossierId);
ManualRedactions unprocessedManualRedactions = manualRedactionProviderService.getManualRedactions(fileId, ManualChangesQueryOptions.unprocessedOnly());
entityLog = entityLogMergeService.mergeEntityLog(unprocessedManualRedactions, entityLog, dossier);
processedEntityLog = entityLogMergeService.mergeEntityLog(unprocessedManualRedactions, processedEntityLog, dossier);
}
if (fileStatus.getExcludedPages() != null && !fileStatus.getExcludedPages().isEmpty()) {
entityLog.getEntityLogEntry()
processedEntityLog.getEntityLogEntry()
.removeIf(entry -> entry.getPositions()
.stream()
.anyMatch(position -> fileStatus.getExcludedPages().contains(position.getPageNumber())) //
@ -79,20 +88,20 @@ public class EntityLogService {
}
Map<String, Integer> commentCountPerAnnotationId = commentService.getCommentCounts(fileId);
entityLog.getEntityLogEntry()
processedEntityLog.getEntityLogEntry()
.forEach(entityLogEntry -> entityLogEntry.setNumberOfComments(commentCountPerAnnotationId.getOrDefault(entityLogEntry.getId(), 0)));
return entityLog;
}
@Observed(name = "EntityLogService", contextualName = "get-entity-log")
public EntityLog getFilteredEntityLog(String dossierId, String fileId, FilteredEntityLogRequest filteredEntityLogRequest) {
if (filteredEntityLogRequest.getSpecifiedDate() == null) {
filteredEntityLogRequest.setSpecifiedDate(OffsetDateTime.MIN);
}
var entityLog = getEntityLog(dossierId, fileId, filteredEntityLogRequest.getExcludedTypes(), false);
EntityLog entityLog = fileManagementStorageService.getEntityLogExcludeTypes(dossierId, fileId, filteredEntityLogRequest.getExcludedTypes());
postProcessEntityLog(dossierId, fileId, entityLog, false);
var entityLogEntry = entityLog.getEntityLogEntry();
Iterator<EntityLogEntry> it = entityLogEntry.iterator();
@ -122,4 +131,22 @@ public class EntityLogService {
return entityLog;
}
@Observed(name = "EntityLogService", contextualName = "get-entity-log")
public EntityLog getEntityLogWithEntriesOnPages(String dossierId, String fileId, List<Integer> pageNumbers) {
EntityLog entityLog = fileManagementStorageService.getEntityLogStartingWithEntriesOnPages(dossierId, fileId, pageNumbers);
postProcessEntityLog(dossierId, fileId, entityLog, false);
return entityLog;
}
@Observed(name = "EntityLogService", contextualName = "get-entity-log")
public EntityLog getEntityLogWithEntriesWithEntriesByAnalysisNumber(String dossierId, String fileId, Integer analysisNumber) {
EntityLog entityLog = fileManagementStorageService.getEntityLogWithEntriesByAnalysisNumber(dossierId, fileId, analysisNumber);
postProcessEntityLog(dossierId, fileId, entityLog, false);
return entityLog;
}
}

View File

@ -6,6 +6,7 @@ import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
@ -19,6 +20,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.imported.ImportedRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.iqser.red.storage.commons.exception.StorageException;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
@ -36,6 +38,8 @@ public class FileManagementStorageService {
private final StorageService storageService;
private final EntityLogMongoService entityLogMongoService;
@SneakyThrows
public byte[] getStoredObjectBytes(String dossierId, String fileId, FileType fileType) {
@ -99,14 +103,50 @@ public class FileManagementStorageService {
public EntityLog getEntityLog(String dossierId, String fileId) {
try {
return storageService.readJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.ENTITY_LOG), EntityLog.class);
} catch (StorageObjectDoesNotExist e) {
log.debug("EntityLog does not exist");
throw new NotFoundException(String.format("EntityLog does not exist for Dossier ID \"%s\" and File ID \"%s\"!", dossierId, fileId));
} catch (StorageException e) {
throw new InternalServerErrorException(e.getMessage());
}
return entityLogMongoService.findEntityLogByDossierIdAndFileId(dossierId, fileId)
.orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));
}
public EntityLog getEntityLogExcludeTypes(String dossierId, String fileId, List<String> excludedTypes) {
return entityLogMongoService.findEntityLogWithoutExcludedEntryTypes(dossierId, fileId, excludedTypes)
.orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));
}
public EntityLog getEntityLogWithEntriesByAnalysisNumber(String dossierId, String fileId, Integer analysisNumber) {
return entityLogMongoService.findEntityLogWithEntriesByAnalysisNumber(dossierId, fileId, analysisNumber)
.orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));
}
public EntityLog getEntityLogStartingWithEntriesOnPages(String dossierId, String fileId, List<Integer> pageNumbers) {
return entityLogMongoService.findEntityLogWithEntriesOnPages(dossierId, fileId, pageNumbers)
.orElseThrow(() -> new NotFoundException(getEntityLogNotFoundErrorMessage(dossierId, fileId)));
}
private static String getEntityLogNotFoundErrorMessage(String dossierId, String fileId) {
return String.format("EntityLog does not exist for Dossier ID \"%s\" and File ID \"%s\"!", dossierId, fileId);
}
@SneakyThrows
public void saveEntityLog(String dossierId, String fileId, EntityLog entityLog) {
entityLogMongoService.upsertEntityLog(dossierId, fileId, entityLog);
}
public boolean entityLogExists(String dossierId, String fileId) {
return entityLogMongoService.entityLogDocumentExists(dossierId, fileId);
}
@ -162,6 +202,12 @@ public class FileManagementStorageService {
}
public void deleteEntityLog(String dossierId, String fileId) {
entityLogMongoService.deleteEntityLog(dossierId, fileId);
}
public void deleteObject(String storageId) {
storageService.deleteObject(TenantContext.getTenantId(), storageId);

View File

@ -690,7 +690,7 @@ public class FileStatusService {
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.ORIGIN);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.REDACTION_LOG);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.ENTITY_LOG);
fileManagementStorageService.deleteEntityLog(dossierId, fileId);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.IMAGE_INFO);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_STRUCTURE);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.DOCUMENT_PAGES);
@ -719,7 +719,7 @@ public class FileStatusService {
OffsetDateTime now = OffsetDateTime.now();
// remove everything related to redaction
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.REDACTION_LOG);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.ENTITY_LOG);
fileManagementStorageService.deleteEntityLog(dossierId, fileId);
fileManagementStorageService.deleteObject(dossierId, fileId, FileType.IMAGE_INFO);
// wipe comments

View File

@ -88,8 +88,7 @@ public class ManualRedactionMapper {
boolean includeUnprocessed) {
List<RemoveRedactionRequest> requests = new ArrayList<>();
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), includeUnprocessed);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, includeUnprocessed);
for (var removeRedactionRequest : removeRedactionRequests) {
EntityLogEntry entityLogEntry = getEntityLogEntry(entityLog, removeRedactionRequest.getAnnotationId());
@ -117,7 +116,7 @@ public class ManualRedactionMapper {
public List<ForceRedactionRequest> toForceRedactionRequestList(String dossierId, String fileId, Set<ForceRedactionRequestModel> forceRedactionRequests) {
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), true);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId,true);
List<ForceRedactionRequest> requests = new ArrayList<>();
for (ForceRedactionRequestModel forceRedactionRequestModel : forceRedactionRequests) {
@ -144,7 +143,7 @@ public class ManualRedactionMapper {
@Deprecated(forRemoval = true)
public List<LegalBasisChangeRequest> toLegalBasisChangeRequestList(String dossierId, String fileId, Set<LegalBasisChangeRequestModel> legalBasisChangeRequests) {
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), true);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, true);
List<LegalBasisChangeRequest> requests = new ArrayList<>();
for (LegalBasisChangeRequestModel legalBasisChangeRequest : legalBasisChangeRequests) {
@ -176,7 +175,7 @@ public class ManualRedactionMapper {
Set<RecategorizationRequestModel> recategorizationRequests,
boolean includeUnprocessed) {
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), includeUnprocessed);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, includeUnprocessed);
List<RecategorizationRequest> requests = new ArrayList<>();
for (RecategorizationRequestModel recategorizationRequest : recategorizationRequests) {
@ -215,7 +214,7 @@ public class ManualRedactionMapper {
Set<ResizeRedactionRequestModel> resizeRedactionRequests,
boolean includeUnprocessed) {
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), includeUnprocessed);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, includeUnprocessed);
List<ResizeRedactionRequest> requests = new ArrayList<>();
for (ResizeRedactionRequestModel resizeRedactionRequest : resizeRedactionRequests) {

View File

@ -4,16 +4,13 @@ import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicC
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.model.ManualChangesQueryOptions;
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.AddRedactionPersistenceService;

View File

@ -5,7 +5,6 @@ import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicC
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -279,7 +278,7 @@ public class ManualRedactionService {
List<ManualAddResponse> response = new ArrayList<>();
List<ManualResizeRedactionEntity> manualResizeRedactionEntities = new ArrayList<>();
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), includeUnprocessed);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, includeUnprocessed);
for (ResizeRedactionRequest resizeRedactionRequest : resizeRedactionRequests) {

View File

@ -12,7 +12,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -206,7 +205,7 @@ public class ManualRedactionUndoService {
private void deleteRecategorization(String dossierId, String fileId, List<String> annotationIds, boolean includeUnprocessed) {
dossierPersistenceService.getAndValidateDossier(dossierId);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), includeUnprocessed);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, includeUnprocessed);
for (var annotationId : annotationIds) {
ManualRecategorizationEntity recategorizationEntity = recategorizationPersistenceService.findRecategorization(fileId, annotationId);
@ -284,7 +283,7 @@ public class ManualRedactionUndoService {
private void deleteRemoveRedaction(String dossierId, String fileId, List<String> annotationIds) {
dossierPersistenceService.getAndValidateDossier(dossierId);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, Collections.emptyList(), true);
EntityLog entityLog = entityLogService.getEntityLog(dossierId, fileId, true);
for (String annotationId : annotationIds) {

View File

@ -0,0 +1,7 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
<include file="/mongo/changelog/tenant/1-initial-database.changelog.xml"/>
<include file="/mongo/changelog/tenant/2-create-indices-for-entries.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,224 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="createEntityLogEntryCollection" author="maverick">
<ext:createCollection collectionName="entity-log-entries">
<ext:options>
{
validator: {
$jsonSchema: {
bsonType: "object",
required: ["entryId", "entityLogId", "type", "entryType", "state", "value", "reason", "matchedRule", "legalBasis", "containingNodeId", "closestHeadline", "section",
"positions", "textBefore", "textAfter", "startOffset", "endOffset", "imageHasTransparency", "dictionaryEntry", "dossierDictionaryEntry", "excluded", "changes",
"manualChanges", "engines", "reference", "importedRedactionIntersections", "numberOfComments"],
properties: {
entryId: {
bsonType: "string",
description: "The Entry ID"
},
entityLogId: {
bsonType: "string",
description: "The Entity Log ID"
},
type: {
bsonType: "string",
description: "The Type"
},
entryType: {
bsonType: "string",
description: "The Entry Type"
},
state: {
bsonType: "string",
description: "The Entry State"
},
value: {
bsonType: "string",
description: "The Value"
},
reason: {
bsonType: "string",
description: "The Reason"
},
matchedRule: {
bsonType: "string",
description: "The Matched Rule"
},
legalBasis: {
bsonType: "string",
description: "The Legal Basis"
},
containingNodeId: {
bsonType: "array",
items: {
bsonType: "int",
description: "The Containing Node ID"
}
},
closestHeadline: {
bsonType: "string",
description: "The Closest Headline"
},
section: {
bsonType: "string",
description: "The Section"
},
positions: {
bsonType: "array",
description: "The Positions",
items: {
bsonType: "object"
}
},
textBefore: {
bsonType: "string",
description: "Text before the entry"
},
textAfter: {
bsonType: "string",
description: "Text after the entry"
},
startOffset: {
bsonType: "int",
description: "Start offset of the entry"
},
endOffset: {
bsonType: "int",
description: "End offset of the entry"
},
imageHasTransparency: {
bsonType: "bool",
description: "Whether the image has transparency"
},
dictionaryEntry: {
bsonType: "bool",
description: "Whether it's a dictionary entry"
},
dossierDictionaryEntry: {
bsonType: "bool",
description: "Whether it's a dossier dictionary entry"
},
excluded: {
bsonType: "bool",
description: "Whether it's excluded"
},
changes: {
bsonType: "array",
description: "The Changes",
items: {
bsonType: "object"
}
},
manualChanges: {
bsonType: "array",
description: "The Manual Changes",
items: {
bsonType: "object"
}
},
engines: {
bsonType: "array",
description: "The Engines",
items: {
bsonType: "string"
}
},
reference: {
bsonType: "array",
description: "The Reference",
items: {
bsonType: "string"
}
},
importedRedactionIntersections: {
bsonType: "array",
description: "The Imported Redaction Intersections",
items: {
bsonType: "string"
}
},
numberOfComments: {
bsonType: "int",
description: "The Number of Comments"
}
}
}
},
validationAction: "warn",
validationLevel: "strict"
}
</ext:options>
</ext:createCollection>
<ext:createCollection collectionName="entity-logs">
<ext:options>
{
validator: {
$jsonSchema: {
bsonType: "object",
required: ["dossierId", "fileId", "analysisVersion", "analysisNumber", "entityLogEntryDocument", "legalBasis"],
properties: {
dossierId: {
bsonType: "string",
description: "The Dossier ID"
},
fileId: {
bsonType: "string",
description: "The File ID"
},
analysisVersion: {
bsonType: "long",
description: "The Analysis Version"
},
analysisNumber: {
bsonType: "int",
description: "The Analysis Number"
},
entityLogEntryDocument: {
bsonType: "array",
description: "The Entity Log Entry Documents",
items: {
bsonType: "objectId"
}
},
legalBasis: {
bsonType: "array",
description: "The Legal Basis",
items: {
bsonType: "object"
}
},
dictionaryVersion: {
bsonType: "long",
description: "The Dictionary Version"
},
dossierDictionaryVersion: {
bsonType: "long",
description: "The Dossier Dictionary Version"
},
rulesVersion: {
bsonType: "long",
description: "The Rules Version"
},
legalBasisVersion: {
bsonType: "long",
description: "The Legal Basis Version"
}
}
}
},
validationAction: "warn",
validationLevel: "strict"
}
</ext:options>
</ext:createCollection>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,17 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="createEntityLogEntryCollection" author="maverick">
<ext:createCollection collectionName="entity-log-entries"/>
<ext:createCollection collectionName="entity-logs"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,48 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="createIndicesForEntries" author="maverick">
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
}
</ext:keys>
<ext:options>
{name: "entityLogId_index"}
</ext:options>
</ext:createIndex>
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
"positions.pageNumber": 1
}
</ext:keys>
<ext:options>
{name: "entityLogId_positionsPageNumber_index"}
</ext:options>
</ext:createIndex>
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
"containingNodeId": 1
}
</ext:keys>
<ext:options>
{name: "entityLogId_containingNodeId_index"}
</ext:options>
</ext:createIndex>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,84 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="createIndicesForEntries" author="maverick">
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
}
</ext:keys>
<ext:options>
{name: "entityLogId_index"}
</ext:options>
</ext:createIndex>
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
"positions.pageNumber": 1
}
</ext:keys>
<ext:options>
{name: "entityLogId_positionsPageNumber_index"}
</ext:options>
</ext:createIndex>
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
"changes.analysisNumber": -1
}
</ext:keys>
<ext:options>
{name: "entityLogId_changesAnalysisNumber_index"}
</ext:options>
</ext:createIndex>
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
"containingNodeId": 1
}
</ext:keys>
<ext:options>
{name: "entityLogId_containingNodeId_index"}
</ext:options>
</ext:createIndex>
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"id": 1,
"containingNodeId": 1
}
</ext:keys>
<ext:options>
{name: "id_containingNodeId_index"}
</ext:options>
</ext:createIndex>
<ext:createIndex collectionName="entity-log-entries">
<ext:keys>
{
"entityLogId": 1,
"type": 1
}
</ext:keys>
<ext:options>
{name: "entityLogId_type_index"}
</ext:options>
</ext:createIndex>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,26 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="1" author="maverick">
<ext:runCommand>
<ext:command>
{
update: "entity-log-entries",
updates: [
{
q: {},
u: { $unset: { "numberOfComments": "" } },
multi: true
}
]
}
</ext:command>
</ext:runCommand>
</changeSet>
</databaseChangeLog>

View File

@ -22,6 +22,7 @@ dependencies {
api(project(":persistence-service-external-api-impl-v1"))
api(project(":persistence-service-external-api-impl-v2"))
api(project(":persistence-service-internal-api-impl-v1"))
api(project(":persistence-service-shared-mongo-v1"))
api("com.iqser.red.commons:storage-commons:2.45.0")
api("junit:junit:4.13.2")
api("org.apache.logging.log4j:log4j-slf4j-impl:2.19.0")

View File

@ -12,8 +12,10 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
@ -21,6 +23,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.http.server.observation.ServerRequestObservationConvention;
@ -43,6 +46,8 @@ import com.iqser.red.storage.commons.StorageAutoConfiguration;
import com.knecon.fforesight.databasetenantcommons.DatabaseTenantCommonsAutoConfiguration;
import com.knecon.fforesight.jobscommons.JobsAutoConfiguration;
import com.knecon.fforesight.keycloakcommons.DefaultKeyCloakCommonsAutoConfiguration;
import com.knecon.fforesight.mongo.database.commons.MongoDatabaseCommonsAutoConfiguration;
import com.knecon.fforesight.mongo.database.commons.liquibase.EnableMongoLiquibase;
import com.knecon.fforesight.swaggercommons.SpringDocAutoConfiguration;
import com.knecon.fforesight.tenantcommons.MultiTenancyAutoConfiguration;
import com.knecon.fforesight.tenantcommons.MultiTenancyMessagingConfiguration;
@ -62,8 +67,10 @@ import lombok.extern.slf4j.Slf4j;
@EnableScheduling
@EnableCaching
@EnableConfigurationProperties({FileManagementServiceSettings.class})
@ImportAutoConfiguration({StorageAutoConfiguration.class, JobsAutoConfiguration.class, DatabaseTenantCommonsAutoConfiguration.class, MultiTenancyAutoConfiguration.class, SpringDocAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class})
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, CassandraAutoConfiguration.class, DataSourceAutoConfiguration.class, LiquibaseAutoConfiguration.class})
@EnableMongoRepositories(basePackages = "com.iqser.red.service.persistence")
@EnableMongoLiquibase
@ImportAutoConfiguration({StorageAutoConfiguration.class, JobsAutoConfiguration.class, DatabaseTenantCommonsAutoConfiguration.class, MultiTenancyAutoConfiguration.class, SpringDocAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class, MongoDatabaseCommonsAutoConfiguration.class})
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class, CassandraAutoConfiguration.class, DataSourceAutoConfiguration.class, LiquibaseAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@Import({PersistenceServiceExternalApiConfigurationV2.class, PersistenceServiceExternalApiConfiguration.class, PersistenceServiceInternalApiConfiguration.class, PersistenceServiceExternalApiCacheConfiguration.class, MultiTenancyWebConfiguration.class, PersistenceServiceProcessorConfiguration.class, MessagingConfiguration.class, MultiTenancyMessagingConfiguration.class})
public class Application implements ApplicationContextAware {

View File

@ -23,6 +23,9 @@ multitenancy:
liquibase:
changeLog: classpath:db/changelog/db.changelog-tenant.yaml
clear-checksums: true
mongo:
liquibase:
changeLog: classpath:mongo/changelog/mongo.changelog-tenant.xml
monitoring:enabled: true
cors.enabled: true

View File

@ -37,6 +37,14 @@ spring:
batch_size: 1000
order_inserts: true
order_updates: true
data:
mongodb:
auto-index-creation: true
database: redaction
host: ${MONGODB_HOST:localhost}
port: 27017
username: ${MONGODB_USER}
password: ${MONGODB_PASSWORD}
cache:
type: redis
mvc:
@ -105,6 +113,10 @@ multitenancy:
liquibase:
changeLog: classpath:db/changelog/db.changelog-tenant.yaml
clear-checksums: true
mongo:
liquibase:
changeLog: classpath:mongo/changelog/mongo.changelog-tenant.xml
bucket4j:

View File

@ -64,9 +64,8 @@ public class FileTesterAndProvider {
assertThat(fileClient.getDossierStatus(dossier.getId()).size()).isGreaterThanOrEqualTo(1);
fileManagementStorageService.storeJSONObject(dossier.getId(),
fileManagementStorageService.saveEntityLog(dossier.getId(),
file.getFileId(),
FileType.ENTITY_LOG,
new EntityLog(1,
1,
List.of(EntityLogEntry.builder()

View File

@ -0,0 +1,62 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogEntryDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.EntityLogDocumentMapper;
import lombok.SneakyThrows;
public class EntityLogDocumentMapperTest {
private final EntityLogDocumentMapper mapper = EntityLogDocumentMapper.INSTANCE;
private final String ENTITY_LOG = "files/entity-log/b2cbdd4dca0aa1aa0ebbfc5cc1462df0.ENTITY_LOG.json";
private static final String TEST_DOSSIER_ID = "91ce8e90-9aec-473c-b8c3-cbe16443ad34";
private static final String TEST_FILE_ID = "b2cbdd4dca0aa1aa0ebbfc5cc1462df0";
@Test
@SneakyThrows
public void testEntityLogMapper() {
var file = new ClassPathResource(String.format(ENTITY_LOG));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLogBefore = objectMapper.readValue(file.getInputStream(), EntityLog.class);
EntityLogDocument entityLogDocument = mapper.toLogDocument(TEST_DOSSIER_ID, TEST_FILE_ID, entityLogBefore);
assertEquals(entityLogDocument.getDossierId(), TEST_DOSSIER_ID);
assertEquals(entityLogDocument.getFileId(), TEST_FILE_ID);
assertEquals(entityLogDocument.getId(), mapper.getLogId(TEST_DOSSIER_ID, TEST_FILE_ID));
Optional<EntityLogEntryDocument> optionalEntityLogEntryDocument = entityLogDocument.getEntityLogEntryDocuments()
.stream()
.findFirst();
assertTrue(optionalEntityLogEntryDocument.isPresent());
assertNotNull(optionalEntityLogEntryDocument.get().getEntityLogId());
assertNotNull(optionalEntityLogEntryDocument.get().getEntryId());
assertEquals(optionalEntityLogEntryDocument.get().getId(),
mapper.getLogEntryId(mapper.getLogId(TEST_DOSSIER_ID, TEST_FILE_ID), optionalEntityLogEntryDocument.get().getEntryId()));
EntityLog entityLogAfter = mapper.fromLogDocument(entityLogDocument);
assertEquals(entityLogBefore, entityLogAfter);
}
}

View File

@ -0,0 +1,291 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import lombok.SneakyThrows;
public class EntityLogMongoServiceTest extends AbstractPersistenceServerServiceTest {
@Autowired
private EntityLogMongoService entityLogMongoService;
private final String ENTITY_LOG1 = "files/entity-log/b2cbdd4dca0aa1aa0ebbfc5cc1462df0.ENTITY_LOG.json";
private final String ENTITY_LOG2 = "files/entity-log/6f2a9d4b5d4e11211fc0d7732fd45b78.ENTITY_LOG.json";
private final String ENTITY_LOG3_BEFORE = "files/entity-log/c3b23116f2d277170d303bfc6fb82e6a-before.ENTITY_LOG.json";
private final String ENTITY_LOG3_AFTER = "files/entity-log/c3b23116f2d277170d303bfc6fb82e6a-after.ENTITY_LOG.json";
private static final String TEST_DOSSIER_ID = "91ce8e90-9aec-473c-b8c3-cbe16443ad34";
private static final String TEST_FILE1_ID = "b2cbdd4dca0aa1aa0ebbfc5cc1462df0";
private static final String TEST_FILE2_ID = "6f2a9d4b5d4e11211fc0d7732fd45b78";
private static final String TEST_FILE3_ID = "c3b23116f2d277170d303bfc6fb82e6a";
@Test
@SneakyThrows
public void testMultiTenantInserts() {
var file = new ClassPathResource(String.format(ENTITY_LOG1));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLogToStore = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLogMongoService.insertEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLogToStore);
var optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
assertTrue(optionalEntityLog.isPresent());
EntityLog entityLogFromStorage = optionalEntityLog.get();
assertEquals(entityLogFromStorage, entityLogToStore);
TenantContext.setTenantId("redaction2");
optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
assertTrue(optionalEntityLog.isEmpty());
file = new ClassPathResource(String.format(ENTITY_LOG2));
entityLogToStore = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLogMongoService.insertEntityLog(TEST_DOSSIER_ID, TEST_FILE2_ID, entityLogToStore);
optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE2_ID);
assertTrue(optionalEntityLog.isPresent());
entityLogFromStorage = optionalEntityLog.get();
assertEquals(entityLogFromStorage, entityLogToStore);
TenantContext.setTenantId("redaction");
optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE2_ID);
assertTrue(optionalEntityLog.isEmpty());
}
@Test
@SneakyThrows
public void testInsertAndDeleteEntityLogDocument() {
var file = new ClassPathResource(String.format(ENTITY_LOG1));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLogToStore = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLogMongoService.insertEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLogToStore);
var optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
assertTrue(optionalEntityLog.isPresent());
EntityLog entityLogFromStorage = optionalEntityLog.get();
assertEquals(entityLogFromStorage, entityLogToStore);
entityLogMongoService.deleteEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID);
optionalEntityLog = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE1_ID);
assertTrue(optionalEntityLog.isEmpty());
}
@Test
@SneakyThrows
public void testFindAndUpdateEntityLogDocument() {
var file = new ClassPathResource(ENTITY_LOG3_BEFORE);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLogMongoService.insertEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);
Optional<EntityLog> found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
assertTrue(found.isPresent());
assertEquals(found.get().getEntityLogEntry().size(), 1706);
assertEquals(entityLogMongoService.findLatestAnalysisNumber(TEST_DOSSIER_ID, TEST_FILE3_ID)
.orElse(-1), 8);
var latest = entityLogMongoService.findLatestAnalysisNumber(TEST_DOSSIER_ID, TEST_FILE3_ID)
.orElseThrow();
var latestChangedEntries = entityLogMongoService.findEntityLogEntriesByAnalysisNumber(TEST_DOSSIER_ID, TEST_FILE3_ID, latest);
assertEquals(latestChangedEntries.size(), 490);
var manuallyChangedEntries = entityLogMongoService.findEntityLogEntriesWithManualChanges(TEST_DOSSIER_ID, TEST_FILE3_ID);
assertEquals(manuallyChangedEntries.size(), 1014);
String NEW_ENTITY_LOG_ENTRY = """
{
"id": "05240998f2e657d739d59d220094702a",
"type": "CBI_author",
"entryType": "ENTITY",
"state": "REMOVED",
"value": "Amato",
"reason": "Author found",
"matchedRule": "CBI.0.0",
"legalBasis": "Article 39(e)(3) of Regulation (EC) No 178/2002",
"imported": false,
"containingNodeId": [
1,
0
],
"closestHeadline": "",
"section": "[1, 0]: Paragraph: Almond R.H. Almond Richard",
"color": null,
"positions": [
{
"rectangle": [
56.8,
639.3,
32.59199,
12.642
],
"pageNumber": 2
}
],
"textBefore": "S. Amarasekare Amarasingham ",
"textAfter": " Amato S.L Amato",
"startOffset": 5137,
"endOffset": 5142,
"imageHasTransparency": false,
"dictionaryEntry": true,
"dossierDictionaryEntry": false,
"excluded": false,
"changes": [
{
"analysisNumber": 1,
"type": "ADDED",
"dateTime": "2024-03-13T08:44:54.811245839Z"
},
{
"analysisNumber": 3,
"type": "REMOVED",
"dateTime": "2024-03-13T08:47:11.339124572Z"
},
{
"analysisNumber": 4,
"type": "REMOVED",
"dateTime": "2024-03-13T08:47:19.694336707Z"
},
{
"analysisNumber": 5,
"type": "REMOVED",
"dateTime": "2024-03-13T08:48:21.670287664Z"
},
{
"analysisNumber": 6,
"type": "REMOVED",
"dateTime": "2024-03-13T08:58:41.082712591Z"
},
{
"analysisNumber": 7,
"type": "REMOVED",
"dateTime": "2024-03-13T08:59:31.444615299Z"
},
{
"analysisNumber": 8,
"type": "REMOVED",
"dateTime": "2024-03-13T09:05:22.006293189Z"
}
],
"manualChanges": [],
"engines": [
"DICTIONARY"
],
"reference": [],
"importedRedactionIntersections": [],
"numberOfComments": 0
}
""";
EntityLogEntry entityLogEntry = objectMapper.readValue(NEW_ENTITY_LOG_ENTRY, EntityLogEntry.class);
entityLogMongoService.insertEntityLogEntries(TEST_DOSSIER_ID, TEST_FILE3_ID, List.of(entityLogEntry));
found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
assertTrue(found.isPresent());
assertEquals(found.get().getEntityLogEntry().size(), 1707);
}
@Test
@SneakyThrows
public void testUpdateEntityLogDocumentByOverride() {
var file = new ClassPathResource(ENTITY_LOG3_BEFORE);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLogMongoService.insertEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);
Optional<EntityLog> found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
assertTrue(found.isPresent());
assertEquals(found.get().getEntityLogEntry().size(), 1706);
file = new ClassPathResource(ENTITY_LOG3_AFTER);
entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLog.setAnalysisNumber(entityLog.getAnalysisNumber() + 1);
entityLogMongoService.upsertEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);
found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
assertTrue(found.isPresent());
assertEquals(found.get().getEntityLogEntry().size(), 1707);
assertEquals(found.get().getAnalysisNumber(), 9);
}
@Test
@SneakyThrows
public void testUpdateEntityLogWithoutEntries() {
var file = new ClassPathResource(ENTITY_LOG3_BEFORE);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLogMongoService.insertEntityLog(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);
Optional<EntityLog> found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
assertTrue(found.isPresent());
assertEquals(found.get().getEntityLogEntry().size(), 1706);
file = new ClassPathResource(ENTITY_LOG3_AFTER);
entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLog.setAnalysisNumber(entityLog.getAnalysisNumber() + 1);
entityLogMongoService.saveEntityLogWithoutEntries(TEST_DOSSIER_ID, TEST_FILE3_ID, entityLog);
found = entityLogMongoService.findEntityLogByDossierIdAndFileId(TEST_DOSSIER_ID, TEST_FILE3_ID);
assertTrue(found.isPresent());
assertEquals(found.get().getEntityLogEntry().size(), 1706);
assertEquals(found.get().getAnalysisNumber(), 9);
}
}

View File

@ -0,0 +1,44 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.iqser.red.service.persistence.management.v1.processor.mapper.EntityLogResponseMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogResponse;
import lombok.SneakyThrows;
public class EntityLogResponseMapperTest {
private final EntityLogResponseMapper mapper = EntityLogResponseMapper.INSTANCE;
private final String ENTITY_LOG = "files/entity-log/b2cbdd4dca0aa1aa0ebbfc5cc1462df0.ENTITY_LOG.json";
@Test
@SneakyThrows
public void testEntityLogMapper() {
var file = new ClassPathResource(String.format(ENTITY_LOG));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLogBefore = objectMapper.readValue(file.getInputStream(), EntityLog.class);
EntityLogResponse entityLogResponseBefore = mapper.toLogResponse(entityLogBefore);
EntityLog entityLogAfter = mapper.fromLogResponse(entityLogResponseBefore);
assertEquals(entityLogBefore, entityLogAfter);
EntityLogResponse entityLogResponseAfter = mapper.toLogResponse(entityLogAfter);
assertEquals(entityLogResponseBefore, entityLogResponseAfter);
}
}

View File

@ -1,8 +1,8 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
@ -398,7 +398,7 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
0,
0);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
assertThat(fileClient.getDossierStatus(dossier.getId()).size()).isEqualTo(1);

View File

@ -138,9 +138,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
Assertions.assertThrows(FeignException.Forbidden.class,
() -> manualRedactionClient.removeRedactionBulk(dossier.getId(),
@ -332,9 +332,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.removeRedactionBulk(dossier.getId(),
file.getId(),
@ -392,9 +392,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.removeRedactionBulk(dossier.getId(),
file.getId(),
@ -447,9 +447,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.removeRedactionBulk(dossier.getId(),
file.getId(),
@ -542,9 +542,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.ENTITY_LOG, entityLog1);
fileManagementStorageService.saveEntityLog(dossier1.getId(), file1.getId(), entityLog1);
var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(eq(file1.getDossierId()), eq(file1.getFileId()), any(), anyBoolean())).thenReturn(entityLog1);
when(entityLogService.getEntityLog(eq(file1.getDossierId()), eq(file1.getFileId()), anyBoolean())).thenReturn(entityLog1);
var entityLog2 = new EntityLog(1,
1,
@ -561,9 +561,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.ENTITY_LOG, entityLog2);
fileManagementStorageService.saveEntityLog(dossier2.getId(), file2.getId(), entityLog2);
var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), any(), anyBoolean())).thenReturn(entityLog2);
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), anyBoolean())).thenReturn(entityLog2);
// resize redaction in dossier 1
var resizeRedactionDosAndAddToAllDos = ResizeRedactionRequestModel.builder()
@ -707,9 +707,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.ENTITY_LOG, entityLog1);
fileManagementStorageService.saveEntityLog(dossier1.getId(), file1.getId(), entityLog1);
var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(eq(file1.getDossierId()), eq(file1.getFileId()), any(), anyBoolean())).thenReturn(entityLog1);
when(entityLogService.getEntityLog(eq(file1.getDossierId()), eq(file1.getFileId()), anyBoolean())).thenReturn(entityLog1);
var entityLog2 = new EntityLog(1,
1,
@ -726,9 +726,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.ENTITY_LOG, entityLog2);
fileManagementStorageService.saveEntityLog(dossier2.getId(), file2.getId(), entityLog2);
var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), any(), anyBoolean())).thenReturn(entityLog2);
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), anyBoolean())).thenReturn(entityLog2);
// resize redaction in dossier 1
var resizeRedactionDosAndAddToAllDos = ResizeRedactionRequestModel.builder()
@ -876,7 +876,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.ENTITY_LOG, entityLog1);
fileManagementStorageService.saveEntityLog(dossier1.getId(), file1.getId(), entityLog1);
var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(file1.getDossierId(), file1.getFileId())).thenReturn(entityLog1);
@ -895,9 +895,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.ENTITY_LOG, entityLog2);
fileManagementStorageService.saveEntityLog(dossier2.getId(), file2.getId(), entityLog2);
var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), any(), anyBoolean())).thenReturn(entityLog2);
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), anyBoolean())).thenReturn(entityLog2);
// resize redaction in dossier dict
var resizeRedactionDosTemp = ResizeRedactionRequestModel.builder()
@ -1042,7 +1042,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.ENTITY_LOG, entityLog1);
fileManagementStorageService.saveEntityLog(dossier1.getId(), file1.getId(), entityLog1);
var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(file1.getDossierId(), file1.getFileId())).thenReturn(entityLog1);
@ -1061,9 +1061,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.ENTITY_LOG, entityLog2);
fileManagementStorageService.saveEntityLog(dossier2.getId(), file2.getId(), entityLog2);
var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build();
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), any(), anyBoolean())).thenReturn(entityLog2);
when(entityLogService.getEntityLog(eq(file2.getDossierId()), eq(file2.getFileId()), anyBoolean())).thenReturn(entityLog2);
// resize redaction in dossier dict
var resizeRedactionDosTemp = ResizeRedactionRequestModel.builder()
@ -1185,9 +1185,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.recategorizeBulk(dossier.getId(),
file.getId(),
@ -1271,9 +1271,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.recategorizeBulk(dossier.getId(),
file.getId(),
@ -1452,9 +1452,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.removeRedactionBulk(dossier.getId(),
file.getId(),
@ -1575,7 +1575,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.forceRedactionBulk(dossier.getId(),
file.getId(),
@ -1683,9 +1683,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.recategorizeBulk(dossier.getId(),
file.getId(),
@ -1795,9 +1795,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.legalBasisChangeBulk(dossier.getId(),
file.getId(),
@ -1905,9 +1905,9 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
manualRedactionClient.legalBasisChangeBulk(dossier.getId(),
file.getId(),
@ -1989,8 +1989,8 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
var recatModel = RecategorizationRequestModel.builder()
.type(type.getType())
@ -2080,8 +2080,8 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
var recatModel = RecategorizationRequestModel.builder()
.type(type.getType())
@ -2137,7 +2137,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
var recategorizationRequestModel = RecategorizationRequestModel.builder()
.type(type2.getType())
@ -2189,7 +2189,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
var legalBasisChangeRequestModel = LegalBasisChangeRequestModel.builder()
.annotationId("annotationId")
@ -2239,7 +2239,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
var resizeRedactionRequestModel = ResizeRedactionRequestModel.builder()
.annotationId("annotationId")
@ -2290,7 +2290,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.ENTITY_LOG, entityLog);
when(entityLogService.getEntityLog(Mockito.any(), Mockito.any(), any(), anyBoolean())).thenReturn(entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
var forceRedactionRequestModel = ForceRedactionRequestModel.builder()
.annotationId("annotationId")

View File

@ -1,42 +1,20 @@
package com.iqser.red.service.peristence.v1.server.integration.utils;
import com.iqser.red.commons.jackson.ObjectMapperFactory;
import com.iqser.red.service.peristence.v1.server.Application;
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.persistence.management.v1.processor.client.pdftronredactionservice.PDFTronClient;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.client.searchservice.SearchClient;
import com.iqser.red.service.persistence.management.v1.processor.client.tenantusermanagementservice.UsersClient;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ApplicationConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.*;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.*;
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.users.UserService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.ApplicationConfig;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.storage.commons.utils.FileSystemBackedStorageService;
import com.knecon.fforesight.databasetenantcommons.providers.TenantCreatedListener;
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantCreatedEvent;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.TenantsClient;
import com.knecon.fforesight.tenantcommons.model.*;
import static com.iqser.red.service.peristence.v1.server.integration.utils.MongoDBTestContainer.MONGO_DATABASE;
import static com.iqser.red.service.peristence.v1.server.integration.utils.MongoDBTestContainer.MONGO_PASSWORD;
import static com.iqser.red.service.peristence.v1.server.integration.utils.MongoDBTestContainer.MONGO_USERNAME;
import static org.mockito.Mockito.when;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.assertj.core.util.Lists;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
@ -53,7 +31,11 @@ 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;
@ -69,13 +51,78 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.iqser.red.commons.jackson.ObjectMapperFactory;
import com.iqser.red.service.peristence.v1.server.Application;
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.persistence.management.v1.processor.client.pdftronredactionservice.PDFTronClient;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.client.searchservice.SearchClient;
import com.iqser.red.service.persistence.management.v1.processor.client.tenantusermanagementservice.UsersClient;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ApplicationConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
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.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.IndexInformationRepository;
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.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.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.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.service.persistence.repository.annotationentity.ForceRedactionRepository;
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.RecategorizationRepository;
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.users.UserService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.ApplicationConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogEntryDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.storage.commons.utils.FileSystemBackedStorageService;
import com.knecon.fforesight.databasetenantcommons.providers.TenantCreatedListener;
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantCreatedEvent;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.TenantsClient;
import com.knecon.fforesight.tenantcommons.model.AuthDetails;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
import static org.mockito.Mockito.when;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ExtendWith(SpringExtension.class)
@ -100,6 +147,12 @@ public abstract class AbstractPersistenceServerServiceTest {
@Autowired
protected StorageService storageService;
@Autowired
protected EntityLogMongoService entityLogMongoService;
@Autowired
protected EntityLogDocumentRepository entityLogDocumentRepository;
@Autowired
protected EntityLogEntryDocumentRepository entityLogEntryDocumentRepository;
@Autowired
protected FileManagementStorageService fileManagementStorageService;
@Autowired
protected DossierTemplateRepository dossierTemplateRepository;
@ -180,9 +233,9 @@ public abstract class AbstractPersistenceServerServiceTest {
@MockBean
private UsersClient usersClient;
@Autowired
private EncryptionDecryptionService encryptionDecryptionService;
protected EncryptionDecryptionService encryptionDecryptionService;
@Autowired
private TenantCreatedListener tenantCreatedListener;
protected TenantCreatedListener tenantCreatedListener;
private static String[] getAllRoles() {
@ -216,7 +269,7 @@ public abstract class AbstractPersistenceServerServiceTest {
@BeforeEach
public void setupOptimize() {
createDefaultTenant();
createTenants();
TenantContext.setTenantId("redaction");
@ -265,9 +318,10 @@ public abstract class AbstractPersistenceServerServiceTest {
}
private void createDefaultTenant() {
private void createTenants() {
var postgreSQLContainerMaster = SpringPostgreSQLTestContainer.getInstance().withDatabaseName("integration-tests-db-master").withUsername("sa").withPassword("sa");
var mongoDbContainer = MongoDBTestContainer.getInstance();
var port = postgreSQLContainerMaster.getJdbcUrl().substring(0, postgreSQLContainerMaster.getJdbcUrl().lastIndexOf('/')).split(":")[3];
@ -296,6 +350,7 @@ public abstract class AbstractPersistenceServerServiceTest {
.numberOfShards("1")
.numberOfReplicas("5")
.build());
redactionTenant.setS3StorageConnection(S3StorageConnection.builder()
.key("key")
.secret("secret")
@ -304,11 +359,65 @@ public abstract class AbstractPersistenceServerServiceTest {
.region("eu")
.endpoint("endpoint")
.build());
redactionTenant.setMongoDBConnection(MongoDBConnection.builder()
.host(mongoDbContainer.getHost())
.port(String.valueOf(mongoDbContainer.getFirstMappedPort()))
.username(MONGO_USERNAME)
.password(encryptionDecryptionService.encrypt(MONGO_PASSWORD))
.database(MONGO_DATABASE)
.build());
var redactionTenant2 = new TenantResponse();
redactionTenant2.setTenantId("redaction2");
redactionTenant2.setGuid("redaction2");
redactionTenant2.setDisplayName("redaction2");
redactionTenant2.setAuthDetails(new AuthDetails());
redactionTenant2.setDatabaseConnection(DatabaseConnection.builder()
.driver("postgresql")
.host(postgreSQLContainerMaster.getHost())
.port(port)
.database("integration-tests-db-master")
.schema("public")
.username("sa")
.password(encryptionDecryptionService.encrypt("sa"))
.build());
redactionTenant2.setSearchConnection(SearchConnection.builder()
.hosts(Set.of("elasticsearchHost"))
.port(9200)
.scheme("https")
.username("elastic")
.numberOfShards("1")
.numberOfReplicas("5")
.build());
redactionTenant2.setS3StorageConnection(S3StorageConnection.builder()
.key("key")
.secret("secret")
.signerType("signerType")
.bucketName("bucketName")
.region("eu")
.endpoint("endpoint")
.build());
redactionTenant2.setMongoDBConnection(MongoDBConnection.builder()
.host(mongoDbContainer.getHost())
.port(String.valueOf(mongoDbContainer.getFirstMappedPort()))
.username(MONGO_USERNAME)
.password(encryptionDecryptionService.encrypt(MONGO_PASSWORD))
.database("redaction2")
.build());
when(tenantsClient.getTenant("redaction")).thenReturn(redactionTenant);
when(tenantsClient.getTenants()).thenReturn(List.of(redactionTenant));
when(tenantsClient.getTenant("redaction2")).thenReturn(redactionTenant2);
when(tenantsClient.getTenants()).thenReturn(List.of(redactionTenant, redactionTenant2));
try {
tenantCreatedListener.createTenant(new TenantCreatedEvent("redaction"));
tenantCreatedListener.createTenant(new TenantCreatedEvent("redaction2"));
} catch (Exception e) {
e.printStackTrace();
@ -385,6 +494,9 @@ public abstract class AbstractPersistenceServerServiceTest {
indexInformationRepository.deleteAll();
applicationConfigRepository.deleteAll();
entityLogEntryDocumentRepository.deleteAll();
entityLogDocumentRepository.deleteAll();
});
}
@ -401,10 +513,19 @@ public abstract class AbstractPersistenceServerServiceTest {
var redisContainer = RedisTestContainer.getInstance();
redisContainer.start();
log.info("Hosts are - Redis: {}, Postgres: {}", redisContainer.getHost(), postgreSQLContainerMaster.getHost());
var mongoInstance = MongoDBTestContainer.getInstance();
mongoInstance.start();
createMongoDBDatabase(mongoInstance, "redaction");
createMongoDBDatabase(mongoInstance, "redaction2");
log.info("Hosts are - Redis: {}, Postgres: {}, MongoDB: {}", redisContainer.getHost(), postgreSQLContainerMaster.getHost(), mongoInstance.getHost());
TestPropertyValues.of("REDIS_PORT=" + redisContainer.getFirstMappedPort(),
"REDIS_HOST=" + redisContainer.getHost(),
"MONGODB_HOST=" + mongoInstance.getHost(),
"MONGODB_PORT=" + mongoInstance.getFirstMappedPort(),
"MONGODB_USER=" + MONGO_USERNAME,
"MONGODB_PASSWORD=" + MONGO_PASSWORD,
"fforesight.jobs.enabled=false",
"fforesight.keycloak.enabled=false").applyTo(configurableApplicationContext.getEnvironment());
@ -412,6 +533,36 @@ public abstract class AbstractPersistenceServerServiceTest {
}
private static void createMongoDBDatabase(MongoDBTestContainer mongoDBTestContainer, String databaseName) {
try (MongoClient mongoClient = MongoClients.create(String.format("mongodb://%s:%s@%s:%s/",
MONGO_USERNAME,
MONGO_PASSWORD,
mongoDBTestContainer.getHost(),
mongoDBTestContainer.getFirstMappedPort()))) {
MongoDatabase database = mongoClient.getDatabase(databaseName);
BsonDocument createUserCommand = new BsonDocument();
createUserCommand.append("createUser", new BsonString(MONGO_USERNAME));
createUserCommand.append("pwd", new BsonString(MONGO_PASSWORD));
BsonArray roles = new BsonArray();
roles.add(new BsonDocument("role", new BsonString("dbOwner")).append("db", new BsonString(databaseName)));
createUserCommand.append("roles", roles);
try {
database.runCommand(createUserCommand);
} catch (MongoCommandException mongoCommandException) {
// ignore user already exists
if (mongoCommandException.getErrorCode() != 51003) {
throw mongoCommandException;
}
}
}
}
@Configuration
@EnableWebSecurity
@EnableMethodSecurity

View File

@ -0,0 +1,35 @@
package com.iqser.red.service.peristence.v1.server.integration.utils;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;
public final class MongoDBTestContainer extends GenericContainer<MongoDBTestContainer> {
private static final String IMAGE_VERSION = "mongo:7.0.2";
public static final Integer MONGO_PORT = 27017;
public static final String MONGO_DATABASE = "redaction";
public static final String MONGO_PASSWORD = "mongopassword";
public static final String MONGO_USERNAME = "mongousername";
private static MongoDBTestContainer mongoDB;
private MongoDBTestContainer() {
super(DockerImageName.parse(IMAGE_VERSION));
}
public static MongoDBTestContainer getInstance() {
if (mongoDB == null) {
mongoDB = new MongoDBTestContainer().withEnv("MONGO_INITDB_ROOT_USERNAME", MONGO_USERNAME)
.withEnv("MONGO_INITDB_ROOT_PASSWORD", MONGO_PASSWORD)
.withEnv("MONGO_INITDB_DATABASE", MONGO_DATABASE)
.withExposedPorts(MONGO_PORT);
}
return mongoDB;
}
}

View File

@ -19,7 +19,14 @@ spring:
order_inserts: true
order_updates: true
open-in-view: true
data:
mongodb:
auto-index-creation: true
database: redaction
host: ${MONGODB_HOST:localhost}
port: 27017
username: ${MONGODB_USER}
password: ${MONGODB_PASSWORD}
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
@ -102,6 +109,9 @@ multitenancy:
prepStmtCacheSqlLimit: 2048
liquibase:
changeLog: classpath:db/changelog/db.changelog-tenant.yaml
mongo:
liquibase:
changeLog: classpath:mongo/changelog/mongo.changelog-tenant.xml

View File

@ -3,6 +3,8 @@ plugins {
id("io.freefair.lombok") version "8.4"
}
val springBootStarterVersion = "3.1.5"
dependencies {
api("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.16.0")
api("com.google.guava:guava:31.1-jre")

View File

@ -1,5 +1,8 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -12,6 +15,11 @@ import lombok.NoArgsConstructor;
public class EntityLogChanges {
private EntityLog entityLog;
private boolean hasChanges;
private List<EntityLogEntry> newEntityLogEntries = new ArrayList<>();
private List<EntityLogEntry> updatedEntityLogEntries = new ArrayList<>();
public boolean hasChanges() {
return !newEntityLogEntries.isEmpty() || !updatedEntityLogEntries.isEmpty();
}
}

View File

@ -0,0 +1,76 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@FieldDefaults(level = AccessLevel.PRIVATE)
public class EntityLogEntryResponse {
String id;
String type;
EntryType entryType;
EntryState state;
String value;
String reason;
String matchedRule;
String legalBasis;
@Deprecated
boolean imported;
List<Integer> containingNodeId;
String closestHeadline;
String section;
@Deprecated
float[] color;
@Builder.Default
List<Position> positions = new ArrayList<>();
String textBefore;
String textAfter;
int startOffset;
int endOffset;
boolean imageHasTransparency;
boolean dictionaryEntry;
boolean dossierDictionaryEntry;
boolean excluded;
@EqualsAndHashCode.Exclude
@Builder.Default
List<Change> changes = new ArrayList<>();
@EqualsAndHashCode.Exclude
@Builder.Default
List<ManualChange> manualChanges = new ArrayList<>();
@Builder.Default
Set<Engine> engines = new HashSet<>();
@Builder.Default
Set<String> reference = new HashSet<>();
@Builder.Default
Set<String> importedRedactionIntersections = new HashSet<>();
}

View File

@ -0,0 +1,34 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EntityLogResponse {
/**
* Version 0 Redaction Logs have manual redactions merged inside them
* Version 1 Redaction Logs only contain system ( rule/dictionary ) redactions. Manual Redactions are merged in at runtime.
*/
private long analysisVersion;
/**
* Which analysis created this redactionLog.
*/
private int analysisNumber;
private List<EntityLogEntryResponse> entityLogEntry = new ArrayList<>();
private List<EntityLogLegalBasis> legalBasis = new ArrayList<>();
private long dictionaryVersion = -1;
private long dossierDictionaryVersion = -1;
private long rulesVersion = -1;
private long legalBasisVersion = -1;
}

View File

@ -0,0 +1,23 @@
plugins {
id("com.iqser.red.service.java-conventions")
id("io.freefair.lombok") version "8.4"
}
val springBootStarterVersion = "3.1.5"
dependencies {
api(project(":persistence-service-shared-api-v1"))
api("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.16.0")
api("com.google.guava:guava:31.1-jre")
api("com.knecon.fforesight:mongo-database-commons:0.5.0")
api("org.springframework.boot:spring-boot-starter-data-mongodb:${springBootStarterVersion}")
api("org.springframework.boot:spring-boot-starter-validation:3.1.3")
testImplementation("com.iqser.red.commons:test-commons:2.1.0")
testImplementation("org.springframework.boot:spring-boot-starter-test:3.0.4")
compileOnly("org.springdoc:springdoc-openapi-ui:1.7.0")
implementation("org.mapstruct:mapstruct:1.5.5.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.5.Final")
}
description = "persistence-service-shared-mongo-v1"

View File

@ -0,0 +1,10 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@AutoConfiguration
public class SharedMongoAutoConfiguration {
}

View File

@ -0,0 +1,48 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.document;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogLegalBasis;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Document(collection = "entity-logs")
public class EntityLogDocument {
@Id
@EqualsAndHashCode.Include
private String id;
private String dossierId;
private String fileId;
private long analysisVersion;
private int analysisNumber;
@DBRef
private List<EntityLogEntryDocument> entityLogEntryDocuments = new ArrayList<>();
private List<EntityLogLegalBasis> legalBasis = new ArrayList<>();
private long dictionaryVersion = -1;
private long dossierDictionaryVersion = -1;
private long rulesVersion = -1;
private long legalBasisVersion = -1;
}

View File

@ -0,0 +1,75 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.document;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Change;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Engine;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryState;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ManualChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
@Document(collection = "entity-log-entries")
public class EntityLogEntryDocument {
@Id
@EqualsAndHashCode.Include
String id;
String entryId;
String entityLogId;
String type;
EntryType entryType;
EntryState state;
String value;
String reason;
String matchedRule;
String legalBasis;
List<Integer> containingNodeId;
String closestHeadline;
String section;
List<Position> positions = new ArrayList<>();
String textBefore;
String textAfter;
int startOffset;
int endOffset;
boolean imageHasTransparency;
boolean dictionaryEntry;
boolean dossierDictionaryEntry;
boolean excluded;
List<Change> changes = new ArrayList<>();
List<ManualChange> manualChanges = new ArrayList<>();
Set<Engine> engines = new HashSet<>();
Set<String> reference = new HashSet<>();
Set<String> importedRedactionIntersections = new HashSet<>();
}

View File

@ -0,0 +1,10 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.exception;
public class EntityLogDocumentNotFoundException extends RuntimeException {
public EntityLogDocumentNotFoundException(String errorMessage) {
super(errorMessage);
}
}

View File

@ -0,0 +1,60 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.stream.Collectors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogEntryDocument;
@Mapper
public interface EntityLogDocumentMapper {
EntityLogDocumentMapper INSTANCE = Mappers.getMapper(EntityLogDocumentMapper.class);
@Mapping(target = "entityLogEntry", source = "entityLogEntryDocuments")
EntityLog fromLogDocument(EntityLogDocument entityLogDocument);
@Mapping(target = "id", source = "entityLogEntryDocument.entryId")
EntityLogEntry fromLogEntryDocument(EntityLogEntryDocument entityLogEntryDocument);
List<EntityLogEntryDocument> toLogEntryDocuments(List<EntityLogEntry> entityLogEntries);
@Mapping(target = "id", expression = "java(getLogId(dossierId, fileId))")
@Mapping(target = "entityLogEntryDocuments", expression ="java(toEntryDocumentList(getLogId(dossierId, fileId), entityLog.getEntityLogEntry()))")
EntityLogDocument toLogDocument(String dossierId, String fileId, EntityLog entityLog);
@Mapping(target = "id", expression = "java(getLogEntryId(entityLogId, entityLogEntry.getId()))")
@Mapping(target = "entryId", source = "entityLogEntry.id")
EntityLogEntryDocument toLogEntryDocument(String entityLogId, EntityLogEntry entityLogEntry);
default List<EntityLogEntryDocument> toEntryDocumentList(String entityLogId, List<EntityLogEntry> entityLogEntries) {
return entityLogEntries.stream()
.map(entityLogEntry -> toLogEntryDocument(entityLogId, entityLogEntry))
.collect(Collectors.toList());
}
default String getLogId(String dossierId, String fileId) {
return dossierId + "/" + fileId;
}
default String getLogEntryId(String entityLogId, String entryId) {
return entityLogId + "/" + entryId;
}
}

View File

@ -0,0 +1,21 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import java.util.Optional;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogDocument;
@Repository
public interface EntityLogDocumentRepository extends MongoRepository<EntityLogDocument, String> {
@Query(value = "{ 'id' : ?0 }", fields = "{ 'analysisNumber' : 1 }")
Optional<EntityLogDocument> findAnalysisNumberById(String id);
@Query(value = "{ 'id': ?0 }", fields = "{ 'entityLogEntryDocuments': 0 }")
Optional<EntityLogDocument> findEntityLogDocumentWithoutEntriesById(String id);
}

View File

@ -0,0 +1,59 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import java.util.Collection;
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogEntryDocument;
@Repository
public interface EntityLogEntryDocumentRepository extends MongoRepository<EntityLogEntryDocument, String> {
@Query("{ 'entityLogId' : ?0, 'manualChanges' : { $exists: true, $not: { $size: 0 } } }")
List<EntityLogEntryDocument> findByEntityLogIdAndManualChangesNotEmpty(String entityLogId);
@Query("{ 'entityLogId' : ?0, 'changes.analysisNumber' : ?1 }")
List<EntityLogEntryDocument> findByEntityLogIdAndChangesAnalysisNumber(String entityLogId, int analysisNumber);
@Query("{ 'entityLogId' : ?0, 'changes.analysisNumber' : ?1 }")
List<EntityLogEntryDocument> findByEntityLogIdAndChangesAnalysisNumberEquals(String entityLogId, Integer analysisNumber);
@Query("{ 'entityLogId' : ?0}")
List<EntityLogEntryDocument> findByEntityLogId(String entityLogId);
@Query("{ 'entityLogId' : ?0, 'containingNodeId' : { $exists: true, $not: { $size: 0 } } }")
List<EntityLogEntryDocument> findByEntityLogIdAndContainingNodeIdNotEmpty(String entityLogId);
@Query("{ 'entityLogId' : ?0 , 'containingNodeId' : { $exists: true, $eq: { $size: 0 } } }")
List<EntityLogEntryDocument> findByEntityLogIdAndContainingNodeIdEmpty(String entityLogId);
@Query(value = "{ 'id' : { $in: ?0 }, 'containingNodeId' : { $exists: true, $not: { $size: 0 } } }", fields = "{ 'containingNodeId': 1 }")
List<EntityLogEntryDocument> findContainingNodeIdForAllByIdAndContainingNodeIdNotEmpty(List<String> ids);
@Query("{ 'entityLogId' : ?0, $or: [ { 'containingNodeId' : { $exists: true, $eq: { $size: 0 } } }, { 'containingNodeId.0' : { $in: ?1 } } ] }")
List<EntityLogEntryDocument> findByEntityLogIdAndNotContainedOrFirstContainedByElementInList(String entityLogId, Collection<Integer> containingNodeIds);
@Query("{'entityLogId' : ?0, 'positions': { $elemMatch: { 'pageNumber': { $in: ?1 } } } }")
List<EntityLogEntryDocument> findByEntityLogIdAndPositionsPageNumberIn(String entityLogId, List<Integer> pageNumbers);
@Query(value = "{ 'entityLogId': ?0, 'type': { '$nin': ?1 } }")
List<EntityLogEntryDocument> findEntityLogDocumentByIdAndExcludedTypes(String entityLogId, List<String> excludedTypes);
@Query(value = "{ 'entityLogId' : ?0}", delete = true)
void deleteByEntityLogId(String entityLogId);
}

View File

@ -0,0 +1,34 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.service;
import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.EntityLogDocumentMapper;
@Service
public class EntityLogDocumentUpdateService {
private final MongoTemplate mongoTemplate;
private final EntityLogDocumentMapper mapper = EntityLogDocumentMapper.INSTANCE;
public EntityLogDocumentUpdateService(MongoTemplate mongoTemplate) {this.mongoTemplate = mongoTemplate;}
public void updateEntityLogDocumentWithoutEntries(String dossierId, String fileId, EntityLog entityLog) {
Document doc = new Document();
mongoTemplate.getConverter().write(mapper.toLogDocument(dossierId, fileId, entityLog), doc);
doc.remove("entityLogEntryDocuments");
Update update = new Update();
doc.forEach(update::set);
mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(mapper.getLogId(dossierId, fileId))), update, EntityLogDocument.class);
}
}

View File

@ -0,0 +1,290 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogEntryDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.exception.EntityLogDocumentNotFoundException;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.EntityLogDocumentMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogEntryDocumentRepository;
@Service
public class EntityLogMongoService {
private final EntityLogDocumentRepository entityLogDocumentRepository;
private final EntityLogEntryDocumentRepository entityLogEntryDocumentRepository;
private final EntityLogDocumentUpdateService entityLogDocumentUpdateService;
private final EntityLogDocumentMapper mapper = EntityLogDocumentMapper.INSTANCE;
public EntityLogMongoService(EntityLogDocumentRepository entityLogDocumentRepository,
EntityLogEntryDocumentRepository entityLogEntryDocumentRepository,
EntityLogDocumentUpdateService entityLogDocumentUpdateService) {
this.entityLogDocumentRepository = entityLogDocumentRepository;
this.entityLogEntryDocumentRepository = entityLogEntryDocumentRepository;
this.entityLogDocumentUpdateService = entityLogDocumentUpdateService;
}
public void insertEntityLog(String dossierId, String fileId, EntityLog entityLog) {
EntityLogDocument entityLogDocument = entityLogDocumentRepository.insert(mapper.toLogDocument(dossierId, fileId, entityLog));
entityLogEntryDocumentRepository.insert(entityLog.getEntityLogEntry()
.stream()
.map(entityLogEntry -> mapper.toLogEntryDocument(entityLogDocument.getId(), entityLogEntry))
.toList());
}
// this does everything : insert when not found and update if found
public void upsertEntityLog(String dossierId, String fileId, EntityLog entityLog) {
Optional<EntityLogDocument> optionalEntityLogDocument = entityLogDocumentRepository.findById(mapper.getLogId(dossierId, fileId));
if (optionalEntityLogDocument.isEmpty()) {
insertEntityLog(dossierId, fileId, entityLog);
return;
}
EntityLogDocument oldEntityLogDocument = optionalEntityLogDocument.get();
List<EntityLogEntryDocument> oldEntityLogEntryDocuments = oldEntityLogDocument.getEntityLogEntryDocuments();
EntityLogDocument newEntityLogDocument = mapper.toLogDocument(dossierId, fileId, entityLog);
List<EntityLogEntryDocument> newEntityLogEntryDocuments = newEntityLogDocument.getEntityLogEntryDocuments();
List<EntityLogEntryDocument> toUpdate = new ArrayList<>(newEntityLogEntryDocuments);
toUpdate.retainAll(oldEntityLogEntryDocuments);
List<EntityLogEntryDocument> toRemove = new ArrayList<>(oldEntityLogEntryDocuments);
toRemove.removeAll(toUpdate);
List<EntityLogEntryDocument> toInsert = new ArrayList<>(newEntityLogEntryDocuments);
toInsert.removeAll(toUpdate);
entityLogEntryDocumentRepository.saveAll(toUpdate);
entityLogEntryDocumentRepository.deleteAll(toRemove);
entityLogEntryDocumentRepository.insert(toInsert);
entityLogDocumentRepository.save(newEntityLogDocument);
}
public void deleteEntityLog(String dossierId, String fileId) {
String entityLogId = mapper.getLogId(dossierId, fileId);
entityLogDocumentRepository.deleteById(entityLogId);
entityLogEntryDocumentRepository.deleteByEntityLogId(entityLogId);
}
public void insertEntityLogEntries(String dossierId, String fileId, List<EntityLogEntry> entityLogEntries) {
String entityLogId = mapper.getLogId(dossierId, fileId);
EntityLogDocument entityLogDocument = getEntityLogDocument(entityLogId);
List<EntityLogEntryDocument> entityLogEntryDocuments = entityLogEntries.stream()
.map(entityLogEntry -> mapper.toLogEntryDocument(entityLogId, entityLogEntry))
.toList();
entityLogDocument.getEntityLogEntryDocuments().addAll(entityLogEntryDocuments);
entityLogEntryDocumentRepository.insert(entityLogEntryDocuments);
entityLogDocumentRepository.save(entityLogDocument);
}
public void updateEntityLogEntries(String dossierId, String fileId, List<EntityLogEntry> entityLogEntries) {
String entityLogId = mapper.getLogId(dossierId, fileId);
entityLogEntryDocumentRepository.saveAll(entityLogEntries.stream()
.map(entityLogEntry -> mapper.toLogEntryDocument(entityLogId, entityLogEntry))
.toList());
}
public void deleteEntityLogEntries(String dossierId, String fileId, List<EntityLogEntry> entityLogEntries) {
String entityLogId = mapper.getLogId(dossierId, fileId);
EntityLogDocument entityLogDocument = getEntityLogDocument(entityLogId);
List<EntityLogEntryDocument> entityLogEntryDocuments = entityLogEntries.stream()
.map(entityLogEntry -> mapper.toLogEntryDocument(entityLogId, entityLogEntry))
.toList();
entityLogDocument.getEntityLogEntryDocuments().removeAll(entityLogEntryDocuments);
entityLogEntryDocumentRepository.deleteAll(entityLogEntryDocuments);
entityLogDocumentRepository.save(entityLogDocument);
}
private EntityLogDocument getEntityLogDocument(String entityLogId) {
Optional<EntityLogDocument> optionalEntityLogDocument = entityLogDocumentRepository.findById(entityLogId);
if (optionalEntityLogDocument.isEmpty()) {
throw new EntityLogDocumentNotFoundException(String.format("Entity log not found for %s", entityLogId));
}
return optionalEntityLogDocument.get();
}
public Optional<EntityLog> findEntityLogByDossierIdAndFileId(String dossierId, String fileId) {
return entityLogDocumentRepository.findById(mapper.getLogId(dossierId, fileId))
.map(mapper::fromLogDocument);
}
public boolean entityLogDocumentExists(String dossierId, String fileId) {
return entityLogDocumentRepository.existsById(mapper.getLogId(dossierId, fileId));
}
public Optional<Integer> findLatestAnalysisNumber(String dossierId, String fileId) {
return entityLogDocumentRepository.findAnalysisNumberById(mapper.getLogId(dossierId, fileId))
.map(EntityLogDocument::getAnalysisNumber);
}
public List<EntityLogEntry> findEntityLogEntriesWithManualChanges(String dossierId, String fileId) {
return entityLogEntryDocumentRepository.findByEntityLogIdAndManualChangesNotEmpty(mapper.getLogId(dossierId, fileId))
.stream()
.map(mapper::fromLogEntryDocument)
.toList();
}
public List<EntityLogEntry> findEntityLogEntriesByAnalysisNumber(String dossierId, String fileId, int analysisNumber) {
return entityLogEntryDocumentRepository.findByEntityLogIdAndChangesAnalysisNumber(mapper.getLogId(dossierId, fileId), analysisNumber)
.stream()
.map(mapper::fromLogEntryDocument)
.toList();
}
public Set<Integer> findFirstContainingNodeIdForEachEntry(String dossierId, String fileId, Collection<String> entryIds) {
String entityLogId = mapper.getLogId(dossierId, fileId);
List<String> ids = entryIds.stream()
.map(entryId -> mapper.getLogEntryId(entityLogId, entryId))
.toList();
return entityLogEntryDocumentRepository.findContainingNodeIdForAllByIdAndContainingNodeIdNotEmpty(ids)
.stream()
.filter(entityLogEntryDocument -> !entityLogEntryDocument.getContainingNodeId().isEmpty())
.map((EntityLogEntryDocument entityLogEntryDocument) -> entityLogEntryDocument.getContainingNodeId()
.get(0))
.collect(Collectors.toSet());
}
public List<EntityLogEntry> findEntityLogEntriesNotContainedOrFirstContainedByElementInList(String dossierId, String fileId, Collection<Integer> nodeIds) {
String entityLogId = mapper.getLogId(dossierId, fileId);
return entityLogEntryDocumentRepository.findByEntityLogIdAndNotContainedOrFirstContainedByElementInList(entityLogId, nodeIds)
.stream()
.map(mapper::fromLogEntryDocument)
.toList();
}
public List<EntityLogEntry> findEntityLogEntriesWithEntryIdsIn(String dossierId, String fileId, Collection<String> entryIds) {
String entityLogId = mapper.getLogId(dossierId, fileId);
List<String> ids = entryIds.stream()
.map(entryId -> mapper.getLogEntryId(entityLogId, entryId))
.toList();
return entityLogEntryDocumentRepository.findAllById(ids)
.stream()
.map(mapper::fromLogEntryDocument)
.toList();
}
public List<EntityLogEntry> findEntityLogEntriesWithContainingNodeIds(String dossierId, String fileId) {
return entityLogEntryDocumentRepository.findByEntityLogIdAndContainingNodeIdNotEmpty(mapper.getLogId(dossierId, fileId))
.stream()
.map(mapper::fromLogEntryDocument)
.toList();
}
public Optional<EntityLog> findEntityLogWithoutEntries(String dossierId, String fileId) {
return entityLogDocumentRepository.findEntityLogDocumentWithoutEntriesById(mapper.getLogId(dossierId, fileId))
.map(mapper::fromLogDocument);
}
public Optional<EntityLog> findEntityLogWithoutExcludedEntryTypes(String dossierId, String fileId, List<String> excludedTypes) {
String entityLogId = mapper.getLogId(dossierId, fileId);
Optional<EntityLog> optionalEntityLog = entityLogDocumentRepository.findEntityLogDocumentWithoutEntriesById(entityLogId)
.map(mapper::fromLogDocument);
optionalEntityLog.ifPresent(entityLog -> entityLog.getEntityLogEntry()
.addAll(entityLogEntryDocumentRepository.findEntityLogDocumentByIdAndExcludedTypes(entityLogId, excludedTypes)
.stream()
.map(mapper::fromLogEntryDocument)
.toList()));
return optionalEntityLog;
}
public Optional<EntityLog> findEntityLogWithEntriesOnPages(String dossierId, String fileId, List<Integer> pageNumbers) {
String entityLogId = mapper.getLogId(dossierId, fileId);
Optional<EntityLog> optionalEntityLog = entityLogDocumentRepository.findEntityLogDocumentWithoutEntriesById(entityLogId)
.map(mapper::fromLogDocument);
optionalEntityLog.ifPresent(entityLog -> entityLog.getEntityLogEntry()
.addAll(entityLogEntryDocumentRepository.findByEntityLogIdAndPositionsPageNumberIn(entityLogId, pageNumbers)
.stream()
.map(mapper::fromLogEntryDocument)
.toList()));
return optionalEntityLog;
}
public Optional<EntityLog> findEntityLogWithEntriesByAnalysisNumber(String dossierId, String fileId, Integer analysisNumber) {
String entityLogId = mapper.getLogId(dossierId, fileId);
Optional<EntityLog> optionalEntityLog = entityLogDocumentRepository.findEntityLogDocumentWithoutEntriesById(entityLogId)
.map(mapper::fromLogDocument);
optionalEntityLog.ifPresent(entityLog -> entityLog.getEntityLogEntry()
.addAll(entityLogEntryDocumentRepository.findByEntityLogIdAndChangesAnalysisNumberEquals(entityLogId, analysisNumber)
.stream()
.map(mapper::fromLogEntryDocument)
.toList()));
return optionalEntityLog;
}
public void saveEntityLogWithoutEntries(String dossierId, String fileId, EntityLog entityLog) {
entityLogDocumentUpdateService.updateEntityLogDocumentWithoutEntries(dossierId, fileId, entityLog);
}
}

View File

@ -0,0 +1,16 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
public class IdentityTest {
@Test
public void mockTest() {
int i = 1;
assertThat(i).isEqualTo(1);
}
}

View File

@ -8,6 +8,7 @@ include(":persistence-service-internal-api-impl-v1")
include(":persistence-service-external-api-v2")
include(":persistence-service-external-api-impl-v2")
include(":persistence-service-internal-api-v1")
include(":persistence-service-shared-mongo-v1")
project(":persistence-service-processor-v1").projectDir = file("persistence-service-v1/persistence-service-processor-v1")
project(":persistence-service-shared-api-v1").projectDir = file("persistence-service-v1/persistence-service-shared-api-v1")
project(":persistence-service-external-api-impl-v1").projectDir = file("persistence-service-v1/persistence-service-external-api-impl-v1")
@ -17,3 +18,4 @@ project(":persistence-service-internal-api-impl-v1").projectDir = file("persiste
project(":persistence-service-external-api-v2").projectDir = file("persistence-service-v1/persistence-service-external-api-v2")
project(":persistence-service-external-api-impl-v2").projectDir = file("persistence-service-v1/persistence-service-external-api-impl-v2")
project(":persistence-service-internal-api-v1").projectDir = file("persistence-service-v1/persistence-service-internal-api-v1")
project(":persistence-service-shared-mongo-v1").projectDir = file("persistence-service-v1/persistence-service-shared-mongo-v1")