RED-8702: Explore document databases to store entityLog

* added response model for entitylog and its entries
This commit is contained in:
maverickstuder 2024-03-26 12:36:21 +01:00
parent d79ca13091
commit 268e3cbf58
10 changed files with 226 additions and 23 deletions

View File

@ -10,10 +10,12 @@ 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,29 @@ 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 = "excludedType", 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, 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));
}
}

View File

@ -12,6 +12,7 @@ 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;
@ -37,16 +38,16 @@ 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);
EntityLogResponse 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 getFilteredEntityLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredEntityLogRequest filteredEntityLogRequest);

View File

@ -59,6 +59,10 @@ dependencies {
api("com.opencsv:opencsv:5.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

@ -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

@ -4,7 +4,6 @@ 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.Objects;
import java.util.Optional;
import org.junit.jupiter.api.Test;
@ -15,13 +14,13 @@ 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.EntityLogMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.EntityLogDocumentMapper;
import lombok.SneakyThrows;
public class EntityLogMapperTest {
public class EntityLogDocumentMapperTest {
private final EntityLogMapper mapper = EntityLogMapper.INSTANCE;
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";

View File

@ -0,0 +1,51 @@
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.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 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 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

@ -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

@ -5,7 +5,6 @@ import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
@ -14,9 +13,9 @@ import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.En
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogEntryDocument;
@Mapper
public interface EntityLogMapper {
public interface EntityLogDocumentMapper {
EntityLogMapper INSTANCE = Mappers.getMapper(EntityLogMapper.class);
EntityLogDocumentMapper INSTANCE = Mappers.getMapper(EntityLogDocumentMapper.class);
@Mapping(target = "entityLogEntry", source = "entityLogEntryDocuments")

View File

@ -11,7 +11,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog
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.EntityLogMapper;
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;
@ -20,7 +20,7 @@ public class EntityLogMongoService {
private final EntityLogDocumentRepository entityLogDocumentRepository;
private final EntityLogEntryDocumentRepository entityLogEntryDocumentRepository;
private final EntityLogMapper mapper = EntityLogMapper.INSTANCE;
private final EntityLogDocumentMapper mapper = EntityLogDocumentMapper.INSTANCE;
public EntityLogMongoService(EntityLogDocumentRepository entityLogDocumentRepository, EntityLogEntryDocumentRepository entityLogEntryDocumentRepository) {