From aab2971d6e03431d7deaae6f244a81bed11cdf4b Mon Sep 17 00:00:00 2001 From: Kilian Schuettler Date: Tue, 29 Aug 2023 18:07:42 +0200 Subject: [PATCH] RED-7317: Endpoint to change entity types of dict-based annotations * major refactor of manual redaction service --- .../controller/ManualRedactionController.java | 16 +- .../controller/RedactionLogController.java | 2 +- .../RedactionLogInternalController.java | 2 +- .../ManualImageRecategorizationEntity.java | 28 +- .../AnalysisFlagsCalculationService.java | 2 +- .../service/FileManagementStorageService.java | 14 +- .../FileStatusProcessingUpdateService.java | 5 +- .../service/RedactionLogService.java | 59 +- ...anualRedactionDictionaryUpdateHandler.java | 109 +++- .../ManualRedactionService.java | 118 ++-- .../AddRedactionPersistenceService.java | 2 +- ...ageRecategorizationPersistenceService.java | 20 + .../RedactionLogMergeService.java | 549 ------------------ .../db/changelog/db.changelog-tenant.yaml | 2 + ...ry-changes-to-manual-recategorization.yaml | 79 +++ .../tests/ManualRedactionTest.java | 32 +- .../integration/tests/RedactionLogTest.java | 47 -- .../AbstractPersistenceServerServiceTest.java | 6 +- .../annotations/AddRedactionRequest.java | 2 +- ... => ManualRequestWithAddToDictionary.java} | 3 +- ...ManualRequestWithRemoveFromDictionary.java | 13 + .../annotations/RecategorizationRequest.java | 16 +- .../annotations/RemoveRedactionRequest.java | 2 +- 23 files changed, 357 insertions(+), 771 deletions(-) delete mode 100644 persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/redactionlog/RedactionLogMergeService.java create mode 100644 persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/tenant/108-added-dictionary-changes-to-manual-recategorization.yaml delete mode 100644 persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RedactionLogTest.java rename persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/{ManualRequestWithDictionary.java => ManualRequestWithAddToDictionary.java} (79%) create mode 100644 persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithRemoveFromDictionary.java diff --git a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/ManualRedactionController.java b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/ManualRedactionController.java index 4ce41002b..548846f37 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/ManualRedactionController.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/ManualRedactionController.java @@ -32,7 +32,9 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Comments; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualAddResponse; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions; +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RecategorizationRequest; import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddCommentRequestModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequestModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ForceRedactionRequestModel; @@ -88,7 +90,7 @@ public class ManualRedactionController implements ManualRedactionResource { .message("Comment was removed.") .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) .build()); - manualRedactionService.deleteComment(fileId, List.of(Long.valueOf(commentId))); + manualRedactionService.deleteComment(dossierId, fileId, List.of(Long.valueOf(commentId))); } @@ -100,6 +102,7 @@ public class ManualRedactionController implements ManualRedactionResource { return manualRedactionService.getManualRedactions(fileId); } + @Override @PreAuthorize("hasAuthority('" + READ_MANUAL_REDACTIONS + "')") public Comments getComments(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) { @@ -118,7 +121,8 @@ public class ManualRedactionController implements ManualRedactionResource { accessControlService.verifyFileIsNotApproved(dossierId, fileId); accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId); - var response = manualRedactionService.addComment(fileId, + var response = manualRedactionService.addComment(dossierId, + fileId, annotationId, CommentRequest.builder().user(KeycloakSecurity.getUserId()).text(addCommentRequest.getText()).build()); @@ -298,15 +302,17 @@ public class ManualRedactionController implements ManualRedactionResource { accessControlService.verifyFileIsNotApproved(dossierId, fileId); accessControlService.verifyUserIsMemberOrApprover(dossierId); - List requests = recategorizationRequests.stream() - .map(recategorizationRequest -> com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RecategorizationRequest.builder() + List requests = recategorizationRequests.stream() + .map(recategorizationRequest -> RecategorizationRequest.builder() .annotationId(recategorizationRequest.getAnnotationId()) .user(KeycloakSecurity.getUserId()) .status(AnnotationStatus.APPROVED) - .typeId(toTypeId(recategorizationRequest.getType(), dossier.getDossierTemplateId())) + .dossierTemplateTypeId(toTypeId(recategorizationRequest.getType(), dossier.getDossierTemplateId())) .comment(recategorizationRequest.getComment()) + .dossierId(dossierId) .addToDictionary(recategorizationRequest.isAddToDictionary()) .addToAllDossiers(recategorizationRequest.isAddToAllDossiers()) + .dictionaryEntryType(DictionaryEntryType.ENTRY) .build()) .collect(Collectors.toList()); diff --git a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RedactionLogController.java b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RedactionLogController.java index e38a34b89..a9aa72d55 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RedactionLogController.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v1/src/main/java/com/iqser/red/persistence/service/v1/external/api/impl/controller/RedactionLogController.java @@ -52,7 +52,7 @@ public class RedactionLogController implements RedactionLogResource { @RequestParam(value = "includeFalsePositives", required = false, defaultValue = "false") boolean includeFalsePositives) { try { - return redactionLogService.getRedactionLog(dossierId, fileId, excludedTypes, withManualRedactions, includeFalsePositives); + return redactionLogService.getRedactionLog(dossierId, fileId, excludedTypes); } catch (FeignException e) { throw processFeignException(e); } diff --git a/persistence-service-v1/persistence-service-internal-api-impl-v1/src/main/java/com/iqser/red/service/persistence/v1/internal/api/controller/RedactionLogInternalController.java b/persistence-service-v1/persistence-service-internal-api-impl-v1/src/main/java/com/iqser/red/service/persistence/v1/internal/api/controller/RedactionLogInternalController.java index 878daa64a..f9c1a9f33 100644 --- a/persistence-service-v1/persistence-service-internal-api-impl-v1/src/main/java/com/iqser/red/service/persistence/v1/internal/api/controller/RedactionLogInternalController.java +++ b/persistence-service-v1/persistence-service-internal-api-impl-v1/src/main/java/com/iqser/red/service/persistence/v1/internal/api/controller/RedactionLogInternalController.java @@ -25,7 +25,7 @@ public class RedactionLogInternalController implements RedactionLogResource { @RequestParam(value = "withManualRedactions", required = false, defaultValue = "true") boolean withManualRedactions, @RequestParam(value = "includeFalsePositives", required = false, defaultValue = "false") boolean includeFalsePositives) { - return redactionLogService.getRedactionLog(dossierId, fileId, excludedTypes, withManualRedactions, includeFalsePositives); + return redactionLogService.getRedactionLog(dossierId, fileId, excludedTypes); } } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/entity/annotations/ManualImageRecategorizationEntity.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/entity/annotations/ManualImageRecategorizationEntity.java index 0bc4031a3..a4900d2a6 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/entity/annotations/ManualImageRecategorizationEntity.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/entity/annotations/ManualImageRecategorizationEntity.java @@ -1,18 +1,24 @@ package com.iqser.red.service.persistence.management.v1.processor.entity.annotations; import java.time.OffsetDateTime; +import java.util.HashSet; +import java.util.Set; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; + +import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity; +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus; + +import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; - -import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -43,8 +49,22 @@ public class ManualImageRecategorizationEntity implements IBaseAnnotation { private OffsetDateTime softDeletedTime; @Column private int page; + @Column + private boolean addToDictionary; + @Column + private boolean addToAllDossiers; @ManyToOne private FileEntity fileStatus; + @ElementCollection + @CollectionTable(name = "manual_recategorization_type_ids_of_dictionaries_with_add") + @Fetch(value = FetchMode.SUBSELECT) + private Set typeIdsOfDictionariesWithAdd = new HashSet<>(); + + @ElementCollection + @CollectionTable(name = "manual_recategorization_type_ids_of_dictionaries_with_delete") + @Fetch(value = FetchMode.SUBSELECT) + private Set typeIdsOfDictionariesWithDelete = new HashSet<>(); + } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java index ee750cc7e..e5f484bf1 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/AnalysisFlagsCalculationService.java @@ -36,7 +36,7 @@ public class AnalysisFlagsCalculationService { long startTime = System.currentTimeMillis(); var file = fileStatusPersistenceService.getStatus(fileId); - var redactionLog = redactionLogService.getRedactionLog(dossierId, fileId, true, false); + var redactionLog = redactionLogService.getRedactionLog(dossierId, fileId); var viewedPagesForCurrentAssignee = viewedPagesPersistenceService.findViewedPages(fileId, file.getAssignee()); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileManagementStorageService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileManagementStorageService.java index 34b234218..27bcc7e41 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileManagementStorageService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileManagementStorageService.java @@ -9,12 +9,14 @@ import java.nio.file.StandardOpenOption; import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Service; +import com.iqser.red.service.persistence.management.v1.processor.exception.InternalServerErrorException; import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType; 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.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; @@ -50,6 +52,7 @@ public class FileManagementStorageService { return storedObjectBytes; } + @SneakyThrows public InputStream getObject(String tenantId, String storageId) { @@ -83,11 +86,20 @@ public class FileManagementStorageService { return storageService.readJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.REDACTION_LOG), RedactionLog.class); } catch (StorageObjectDoesNotExist e) { log.debug("RedactionLog does not exist"); - throw new NotFoundException("RedactionLog does not exist"); + throw new NotFoundException(String.format("RedactionLog does not exist for Dossier ID \"%s\" and File ID \"%s\"!", dossierId, fileId)); + } catch (StorageException e) { + throw new InternalServerErrorException(e.getMessage()); } } + public void storeRedactionLog(String dossierId, String fileId, RedactionLog redactionLog) { + + storageService.storeJSONObject(TenantContext.getTenantId(), StorageIdUtils.getStorageId(dossierId, fileId, FileType.REDACTION_LOG), redactionLog); + + } + + public SectionGrid getSectionGrid(String dossierId, String fileId) { try { diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileStatusProcessingUpdateService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileStatusProcessingUpdateService.java index 0693c05ad..258e3de36 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileStatusProcessingUpdateService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/FileStatusProcessingUpdateService.java @@ -1,7 +1,5 @@ package com.iqser.red.service.persistence.management.v1.processor.service; -import jakarta.transaction.Transactional; - import org.apache.commons.lang3.StringUtils; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.bind.annotation.RestController; @@ -17,6 +15,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus; import com.iqser.red.service.search.v1.model.IndexMessageType; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -54,7 +53,7 @@ public class FileStatusProcessingUpdateService { indexingService.addToIndexingQueue(IndexMessageType.INSERT, dossier.getDossierTemplateId(), dossierId, fileId, 2); } - manualRedactionService.updateProcessedDate(fileId, analyzeResult.getManualRedactions()); + manualRedactionService.updateProcessedDate(analyzeResult.getManualRedactions()); analysisFlagsCalculationService.calculateFlags(dossierId, fileId); if (analyzeResult.getAddedFileAttributes() != null && !analyzeResult.getAddedFileAttributes().isEmpty()) { diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/RedactionLogService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/RedactionLogService.java index 252560442..cc6923749 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/RedactionLogService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/RedactionLogService.java @@ -2,26 +2,17 @@ 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 org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; -import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService; -import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService; -import com.iqser.red.service.persistence.management.v1.processor.service.redactionlog.RedactionLogMergeService; -import com.iqser.red.service.persistence.management.v1.processor.service.redactionlog.RedactionRequest; -import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter; -import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors; -import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.FilteredRedactionLogRequest; 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.RedactionLogEntry; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid; -import feign.FeignException; import lombok.RequiredArgsConstructor; @Service @@ -29,59 +20,29 @@ import lombok.RequiredArgsConstructor; public class RedactionLogService { private final FileManagementStorageService fileManagementStorageService; - private final ManualRedactionProviderService manualRedactionService; - private final DossierPersistenceService dossierPersistenceService; private final FileStatusService fileStatusService; - private final ColorsService colorsService; - private final DictionaryPersistenceService dictionaryPersistenceService; - private final RedactionLogMergeService redactionLogMergeService; - public RedactionLog getRedactionLog(String dossierId, String fileId, boolean withManualRedactions, boolean includeFalsePositives) { + public RedactionLog getRedactionLog(String dossierId, String fileId) { - return getRedactionLog(dossierId, fileId, null, withManualRedactions, includeFalsePositives); + return getRedactionLog(dossierId, fileId, Collections.emptyList()); } - public RedactionLog getRedactionLog(String dossierId, String fileId, List excludedTypes, boolean withManualRedactions, boolean includeFalsePositives) { + public RedactionLog getRedactionLog(String dossierId, String fileId, List excludedTypes) { var fileStatus = fileStatusService.getStatus(fileId); RedactionLog redactionLog; - if (withManualRedactions) { - var dossier = dossierPersistenceService.findByDossierId(dossierId); - var manualRedactions = manualRedactionService.getManualRedactions(fileId); - var colors = MagicConverter.convert(colorsService.getColors(dossier.getDossierTemplateId()), Colors.class); - var types = MagicConverter.convert(dictionaryPersistenceService.getAllTypesForDossierTemplate(dossier.getDossierTemplateId(), true), Type.class); - var dossierTypes = MagicConverter.convert(dictionaryPersistenceService.getAllTypesForDossier(dossierId, true), Type.class); - types.addAll(dossierTypes); - - try { - redactionLog = redactionLogMergeService.provideRedactionLog(RedactionRequest.builder() - .dossierId(dossierId) - .fileId(fileId) - .manualRedactions(manualRedactions) - .dossierTemplateId(dossier.getDossierTemplateId()) - .excludedPages(fileStatus.getExcludedPages()) - .includeFalsePositives(includeFalsePositives) - .colors(colors) - .types(types) - .build()); - } catch (FeignException e) { - if (e.status() == HttpStatus.NOT_FOUND.value()) { - throw new NotFoundException(e.getMessage()); - } - throw e; - } - } else { - redactionLog = fileManagementStorageService.getRedactionLog(dossierId, fileId); - } + redactionLog = fileManagementStorageService.getRedactionLog(dossierId, fileId); if (fileStatus.isExcluded()) { redactionLog.setRedactionLogEntry(new ArrayList<>()); } + if (fileStatus.isHasUpdates()) + if (excludedTypes != null) { redactionLog.getRedactionLogEntry().removeIf(nextEntry -> excludedTypes.contains(nextEntry.getType())); } @@ -102,11 +63,7 @@ public class RedactionLogService { filteredRedactionLogRequest.setSpecifiedDate(OffsetDateTime.MIN); } - var redactionLog = getRedactionLog(dossierId, - fileId, - filteredRedactionLogRequest.getExcludedTypes(), - filteredRedactionLogRequest.isWithManualRedactions(), - filteredRedactionLogRequest.isIncludeFalsePositives()); + var redactionLog = getRedactionLog(dossierId, fileId, filteredRedactionLogRequest.getExcludedTypes()); var redactionLogEntries = redactionLog.getRedactionLogEntry(); Iterator it = redactionLogEntries.iterator(); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionDictionaryUpdateHandler.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionDictionaryUpdateHandler.java index 71109936c..14d26e786 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionDictionaryUpdateHandler.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionDictionaryUpdateHandler.java @@ -1,19 +1,26 @@ package com.iqser.red.service.persistence.management.v1.processor.service.manualredactions; -import java.util.Collections; +import static com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils.toTypeId; + import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.stereotype.Service; -import com.iqser.red.service.dictionarymerge.commons.DictionaryEntry; import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity; import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException; import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.AddRedactionPersistenceService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ResizeRedactionPersistenceService; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRequestWithDictionary; +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRequestWithAddToDictionary; +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRequestWithRemoveFromDictionary; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType; +import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry; import feign.FeignException; import lombok.RequiredArgsConstructor; @@ -25,33 +32,46 @@ import lombok.extern.slf4j.Slf4j; public class ManualRedactionDictionaryUpdateHandler { private final DictionaryManagementService dictionaryManagementService; + private final AddRedactionPersistenceService addRedactionPersistenceService; + private final FileStatusPersistenceService fileStatusPersistenceService; + private final ResizeRedactionPersistenceService resizeRedactionPersistenceService; + private final DossierPersistenceService dossierPersistenceService; - public Set handleAddToDictionary(String fileId, String dossierId, ManualRequestWithDictionary manualRequestWithDictionary) { + public Set handleAddToDictionaryAndReturnModifiedTypeIds(String fileId, String annotationId, ManualRequestWithAddToDictionary manualRequestWithAddToDictionary) { - if (!manualRequestWithDictionary.isApproved()) { - return Collections.emptySet(); + if (!manualRequestWithAddToDictionary.isApproved()) { + return null; + } + + if (!manualRequestWithAddToDictionary.isAddToDictionary()) { + addRedactionPersistenceService.updateStatus(fileId, annotationId, manualRequestWithAddToDictionary.getStatus(), false, null); + return null; } Set typeIdsOfModifiedDictionaries = new HashSet<>(); - - String dossierTemplateTypeId = manualRequestWithDictionary.getDossierTemplateTypeId(); - String value = manualRequestWithDictionary.getValue(); - DictionaryEntryType dictionaryEntryType = manualRequestWithDictionary.getDictionaryEntryType(); - - if (manualRequestWithDictionary.isAddToAllDossiers()) { - List dictionaryEntriesToUnDelete = dictionaryManagementService.getAllEntriesInDossierTemplate(dossierTemplateTypeId, value); + if (manualRequestWithAddToDictionary.isAddToAllDossiers()) { + var dictionaryEntriesToUnDelete = dictionaryManagementService.getAllEntriesInDossierTemplate(manualRequestWithAddToDictionary.getDossierTemplateTypeId(), + manualRequestWithAddToDictionary.getValue()); dictionaryEntriesToUnDelete.forEach(entry -> { typeIdsOfModifiedDictionaries.add(entry.getTypeId()); - addToDictionary(entry.getTypeId(), manualRequestWithDictionary.getValue(), dossierId, fileId, manualRequestWithDictionary.getDictionaryEntryType()); + addToDictionary(entry.getTypeId(), manualRequestWithAddToDictionary.getValue(), manualRequestWithAddToDictionary.getDossierId(), fileId, manualRequestWithAddToDictionary.getDictionaryEntryType()); }); - addToDictionary(dossierTemplateTypeId, value, dossierId, fileId, dictionaryEntryType); - typeIdsOfModifiedDictionaries.add(dossierTemplateTypeId); - } else { - addToDictionary(dossierTemplateTypeId + ":" + dossierId, value, dossierId, fileId, dictionaryEntryType); - typeIdsOfModifiedDictionaries.add(dossierTemplateTypeId + ":" + dossierId); + addToDictionary(manualRequestWithAddToDictionary.getDossierTemplateTypeId(), + manualRequestWithAddToDictionary.getValue(), + manualRequestWithAddToDictionary.getDossierId(), + fileId, + manualRequestWithAddToDictionary.getDictionaryEntryType()); + typeIdsOfModifiedDictionaries.add(manualRequestWithAddToDictionary.getDossierTemplateTypeId()); + return typeIdsOfModifiedDictionaries; } - + addToDictionary(manualRequestWithAddToDictionary.getDossierTemplateTypeId() + ":" + manualRequestWithAddToDictionary.getDossierId(), + manualRequestWithAddToDictionary.getValue(), + manualRequestWithAddToDictionary.getDossierId(), + fileId, + manualRequestWithAddToDictionary.getDictionaryEntryType()); + typeIdsOfModifiedDictionaries.add(manualRequestWithAddToDictionary.getDossierTemplateTypeId() + ":" + manualRequestWithAddToDictionary.getDossierId()); return typeIdsOfModifiedDictionaries; + } @@ -89,4 +109,53 @@ public class ManualRedactionDictionaryUpdateHandler { } } + + public Set handleRemoveFromDictionaryAndReturnModifiedTypeIds(RedactionLogEntry redactionLogEntry, + String fileId, + String dossierId, + String dossierTemplateId, + ManualRequestWithRemoveFromDictionary manualRequestWithRemoveFromDictionary) { + + if (!manualRequestWithRemoveFromDictionary.isApproved()) { + return null; + } + + if (!manualRequestWithRemoveFromDictionary.isRemoveFromDictionary()) { + + return null; + } + + Set typeIdsOfModifiedDictionaries = new HashSet<>(); + if (manualRequestWithRemoveFromDictionary.isRemoveFromAllDossiers()) { + var dictionaryEntriesToRemove = dictionaryManagementService.getAllEntriesInDossierTemplate(toTypeId(redactionLogEntry.getType(), dossierTemplateId), + redactionLogEntry.getValue()); + dictionaryEntriesToRemove.forEach(entry -> { + typeIdsOfModifiedDictionaries.add(entry.getTypeId()); + removeFromDictionary(entry.getTypeId(), entry.getValue(), dossierId, fileId, DictionaryEntryType.ENTRY); + }); + } else { + typeIdsOfModifiedDictionaries.add(toTypeId(redactionLogEntry.getType(), dossierTemplateId, dossierId)); + removeFromDictionary(toTypeId(redactionLogEntry.getType(), dossierTemplateId, dossierId), redactionLogEntry.getValue(), dossierId, fileId, DictionaryEntryType.ENTRY); + } + + // This is needed to remove resizeRedactions with addToDictionary. + removeResizeRedactionsWithAddToDictionary(dossierTemplateId, redactionLogEntry.getValue()); + + return typeIdsOfModifiedDictionaries; + } + + + private void removeResizeRedactionsWithAddToDictionary(String dossierTemplateId, String redactionLogEntryValue) { + + var resizeRedactionsWithSameValue = resizeRedactionPersistenceService.findByAnnotationStatusAndValue(AnnotationStatus.APPROVED, redactionLogEntryValue); + resizeRedactionsWithSameValue.forEach(resizeRedaction -> { + var file = fileStatusPersistenceService.getStatus(resizeRedaction.getId().getFileId()); + var dossierForResizeRedaction = dossierPersistenceService.findByDossierId(file.getDossierId()); + if (!file.getWorkflowStatus().equals(WorkflowStatus.APPROVED) && dossierTemplateId.equals(dossierForResizeRedaction.getDossierTemplateId())) { + resizeRedactionPersistenceService.hardDelete(resizeRedaction.getId().getFileId(), resizeRedaction.getId().getAnnotationId()); + } + }); + } + + } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionService.java index 5310ed49c..c5f64a39d 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionService.java @@ -54,15 +54,15 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.LegalBasisChangeRequest; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualAddResponse; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRequestWithDictionary; +import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRequestWithAddToDictionary; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RecategorizationRequest; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RemoveRedactionRequest; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ResizeRedactionRequest; import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.IdRemoval; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus; -import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType; 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.RedactionLogComment; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry; import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter; @@ -110,7 +110,16 @@ public class ManualRedactionService { String annotationId = hashFunction.hashString(fileId + addRedactionRequest, StandardCharsets.UTF_8).toString(); addRedactionPersistenceService.insert(fileId, annotationId, addRedactionRequest); - manualRedactionDictionaryUpdateHandler.handleAddToDictionary(fileId, annotationId, addRedactionRequest); + + Set typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId, + annotationId, + addRedactionRequest); + + addRedactionPersistenceService.updateStatus(fileId, + annotationId, + addRedactionRequest.getStatus(), + typeIdsOfModifiedDictionaries != null, + typeIdsOfModifiedDictionaries); Long commentId = addCommentAndGetId(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser()); @@ -127,7 +136,7 @@ public class ManualRedactionService { } - private void validateDictionaries(ManualRequestWithDictionary addRedactionRequest) { + private void validateDictionaries(ManualRequestWithAddToDictionary addRedactionRequest) { if (addRedactionRequest.isAddToDictionary()) { if (addRedactionRequest.isAddToAllDossiers()) { @@ -139,7 +148,7 @@ public class ManualRedactionService { } - private void validateDictionary(ManualRequestWithDictionary addRedactionRequest) { + private void validateDictionary(ManualRequestWithAddToDictionary addRedactionRequest) { try { if (!addRedactionRequest.isForceAddToDictionary() && stopwordService.isStopword(addRedactionRequest.getValue())) { @@ -263,11 +272,20 @@ public class ManualRedactionService { requiresReAnalysis = requiresReAnalysis || matchingEntryFound; } - boolean removedFromDictionary = handleRemoveFromDictionary(getRedactionLogEntry(redactionLog, removeRedactionRequest.getAnnotationId()), - dossier, - fileId, + Set typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(// + getRedactionLogEntry(redactionLog, removeRedactionRequest.getAnnotationId()),// + dossierId, // + dossier.getDossierTemplateId(),// + fileId, // removeRedactionRequest); + boolean removedFromDictionary = typeIdsOfModifiedDictionaries != null; + removeRedactionPersistenceService.updateStatus(fileId, + removeRedactionRequest.getAnnotationId(), + removeRedactionRequest.getStatus(), + removedFromDictionary, + typeIdsOfModifiedDictionaries); + if (!matchingEntryFound && !removedFromDictionary && idRemoval.isApproved()) { removeRedactionPersistenceService.markAsProcessed(idRemoval); } @@ -311,40 +329,7 @@ public class ManualRedactionService { } - private boolean handleRemoveFromDictionary(RedactionLogEntry redactionLogEntry, DossierEntity dossier, String fileId, RemoveRedactionRequest removeRedactionRequest) { - if (!removeRedactionRequest.isApproved()) { - return false; - } - - if (!removeRedactionRequest.isRemoveFromDictionary()) { - removeRedactionPersistenceService.updateStatus(fileId, removeRedactionRequest.getAnnotationId(), removeRedactionRequest.getStatus(), false, Collections.emptySet()); - return false; - } - - Set typeIdsOfModifiedDictionaries = new HashSet<>(); - if (removeRedactionRequest.isRemoveFromAllDossiers()) { - var dictionaryEntriesToRemove = dictionaryManagementService.getAllEntriesInDossierTemplate(toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId()), - redactionLogEntry.getValue()); - dictionaryEntriesToRemove.forEach(entry -> { - typeIdsOfModifiedDictionaries.add(entry.getTypeId()); - removeFromDictionary(entry.getTypeId(), entry.getValue(), dossier.getId(), fileId, DictionaryEntryType.ENTRY); - }); - } else { - typeIdsOfModifiedDictionaries.add(toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId(), dossier.getId())); - removeFromDictionary(toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId(), dossier.getId()), - redactionLogEntry.getValue(), - dossier.getId(), - fileId, - DictionaryEntryType.ENTRY); - } - - // This is needed to remove resizeRedactions with addToDictionary. - removeResizeRedactionsWithAddToDictionary(dossier.getDossierTemplateId(), redactionLogEntry.getValue()); - - removeRedactionPersistenceService.updateStatus(fileId, removeRedactionRequest.getAnnotationId(), removeRedactionRequest.getStatus(), true, typeIdsOfModifiedDictionaries); - return true; - } private RedactionLogEntry getRedactionLogEntry(RedactionLog redactionLog, String annotationId) { @@ -357,18 +342,6 @@ public class ManualRedactionService { } - private void removeResizeRedactionsWithAddToDictionary(String dossierTemplateId, String redactionLogEntryValue) { - - var resizeRedactionsWithSameValue = resizeRedactionPersistenceService.findByAnnotationStatusAndValue(AnnotationStatus.APPROVED, redactionLogEntryValue); - resizeRedactionsWithSameValue.forEach(resizeRedaction -> { - var file = fileStatusPersistenceService.getStatus(resizeRedaction.getId().getFileId()); - var dossierForResizeRedaction = dossierPersistenceService.findByDossierId(file.getDossierId()); - if (!file.getWorkflowStatus().equals(WorkflowStatus.APPROVED) && dossierTemplateId.equals(dossierForResizeRedaction.getDossierTemplateId())) { - resizeRedactionPersistenceService.hardDelete(resizeRedaction.getId().getFileId(), resizeRedaction.getId().getAnnotationId()); - } - }); - } - @Transactional public List addForceRedaction(String dossierId, String fileId, List forceRedactionRequests) { @@ -420,11 +393,31 @@ public class ManualRedactionService { var response = new ArrayList(); var requiresReanalysis = false; - dossierPersistenceService.getAndValidateDossier(dossierId); - + var dossier = dossierPersistenceService.getAndValidateDossier(dossierId); + RedactionLog redactionLog = fileManagementStorageService.getRedactionLog(dossierId, fileId); for (var recategorizationRequest : recategorizationRequests) { + validateDictionaries(recategorizationRequest); + recategorizationPersistenceService.insert(fileId, recategorizationRequest); + Set typeIdsOfDictionariesWithAdd = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId, + recategorizationRequest.getAnnotationId(), + recategorizationRequest); + + Set typeIdsOfDictionariesWithDelete = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(// + getRedactionLogEntry(redactionLog, recategorizationRequest.getAnnotationId()),// + recategorizationRequest.getDossierId(),// + dossier.getDossierTemplateId(),// + fileId,// + recategorizationRequest); + + recategorizationPersistenceService.updateStatus(fileId, + recategorizationRequest.getAnnotationId(), + AnnotationStatus.APPROVED, + typeIdsOfDictionariesWithAdd != null, + typeIdsOfDictionariesWithAdd, + typeIdsOfDictionariesWithDelete); + Long commentId = addCommentAndGetId(fileId, recategorizationRequest.getAnnotationId(), recategorizationRequest.getComment(), recategorizationRequest.getUser()); requiresReanalysis = requiresReanalysis || recategorizationRequest.getStatus().equals(AnnotationStatus.APPROVED); @@ -441,12 +434,23 @@ public class ManualRedactionService { } - public CommentEntity addComment(String fileId, String annotationId, CommentRequest commentRequest) { + public CommentEntity addComment(String dossierId, String fileId, String annotationId, CommentRequest commentRequest) { CommentEntity createdComment = addComment(fileId, annotationId, commentRequest.getText(), commentRequest.getUser()); fileStatusPersistenceService.updateHasComments(fileId, true); - + if (!fileStatusService.getStatus(fileId).getProcessingStatus().equals(ProcessingStatus.PROCESSED)) { + reprocess(dossierId, fileId); + return createdComment; + } + fileStatusService.setStatusProcessing(fileId); + RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId); + redactionLog.getRedactionLogEntry() + .stream() + .filter(entry -> entry.getId().equals(annotationId)) + .forEach(entry -> entry.getComments().add(MagicConverter.convert(createdComment, RedactionLogComment.class))); + fileManagementStorageService.storeRedactionLog(dossierId, fileId, redactionLog); + fileStatusService.setStatusProcessed(fileId); return createdComment; } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/AddRedactionPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/AddRedactionPersistenceService.java index 20b98dd31..050056a39 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/AddRedactionPersistenceService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/AddRedactionPersistenceService.java @@ -119,7 +119,7 @@ public class AddRedactionPersistenceService { boolean isAddOrRemoveFromDictionary, Set typeIdsOfModifiedDictionaries) { - var addRedaction = manualRedactionRepository.findById(new AnnotationEntityId(annotationId, fileId)) + ManualRedactionEntryEntity addRedaction = manualRedactionRepository.findById(new AnnotationEntityId(annotationId, fileId)) .orElseThrow(() -> new NotFoundException("Unknown file/annotation combination: " + fileId + "/" + annotationId)); addRedaction.setStatus(annotationStatus); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/ImageRecategorizationPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/ImageRecategorizationPersistenceService.java index 632b6d3cf..a2151494c 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/ImageRecategorizationPersistenceService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/annotations/ImageRecategorizationPersistenceService.java @@ -3,6 +3,7 @@ package com.iqser.red.service.persistence.management.v1.processor.service.persis import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.Set; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; @@ -43,6 +44,25 @@ public class ImageRecategorizationPersistenceService { imageRecategorizationRepository.updateStatus(new AnnotationEntityId(annotationId, fileId), annotationStatus); } + @Transactional + public void updateStatus(String fileId, + String annotationId, + AnnotationStatus annotationStatus, + boolean modifiedDictionary, + Set typeIdsOfDictionaryWithAdd, + Set typeIdsOfDictionaryWithDelete) { + + ManualImageRecategorizationEntity addRedaction = imageRecategorizationRepository.findById(new AnnotationEntityId(annotationId, fileId)) + .orElseThrow(() -> new NotFoundException("Unknown file/annotation combination: " + fileId + "/" + annotationId)); + + addRedaction.setStatus(annotationStatus); + addRedaction.setAddToDictionary(modifiedDictionary); + addRedaction.setTypeIdsOfDictionariesWithAdd(typeIdsOfDictionaryWithAdd); + addRedaction.setTypeIdsOfDictionariesWithDelete(typeIdsOfDictionaryWithDelete); + + imageRecategorizationRepository.saveAndFlush(addRedaction); + } + @Transactional public void hardDelete(String fileId, String annotationId) { diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/redactionlog/RedactionLogMergeService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/redactionlog/RedactionLogMergeService.java deleted file mode 100644 index 31f11bce4..000000000 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/redactionlog/RedactionLogMergeService.java +++ /dev/null @@ -1,549 +0,0 @@ -package com.iqser.red.service.persistence.management.v1.processor.service.redactionlog; - -import java.time.OffsetDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.stereotype.Service; - -import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; -import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService; -import com.iqser.red.service.persistence.management.v1.processor.utils.ColorUtils; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Comment; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.IdRemoval; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualForceRedaction; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualImageRecategorization; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualLegalBasisChange; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRedactionEntry; -import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction; -import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors; -import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Change; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ChangeType; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Engine; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ManualChange; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.ManualRedactionType; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Point; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle; -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.RedactionLogComment; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid; - -import io.micrometer.core.annotation.Timed; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@Service -@RequiredArgsConstructor -public class RedactionLogMergeService { - - private static final String DELETED_TYPE_COLOR = "#9398a0"; - private final SectionTextService sectionTextService; - private final FileManagementStorageService fileManagementStorageService; - - - @Timed("redactmanager_getMergedRedactionLog") - public RedactionLog provideRedactionLog(RedactionRequest redactionRequest) { - - log.debug("Requested preview for: {}", redactionRequest); - - var redactionLog = fileManagementStorageService.getRedactionLog(redactionRequest.getDossierId(), redactionRequest.getFileId()); - - if (redactionLog == null) { - throw new NotFoundException("RedactionLog not present"); - } - - log.debug("Loaded redaction log with computationalVersion: {}", redactionLog.getAnalysisVersion()); - var merged = mergeRedactionLogData(redactionLog, - redactionRequest.getManualRedactions(), - redactionRequest.getExcludedPages(), - redactionRequest.getTypes(), - redactionRequest.getColors()); - - merged.getRedactionLogEntry().removeIf(e -> e.isFalsePositive() && !redactionRequest.isIncludeFalsePositives()); - - return merged; - } - - - private RedactionLog mergeRedactionLogData(RedactionLog redactionLog, ManualRedactions manualRedactions, Set excludedPages, List types, Colors colors) { - - var skippedImportedRedactions = new HashSet<>(); - log.info("Merging Redaction log with manual redactions"); - if (manualRedactions != null) { - - var manualRedactionWrappers = createManualRedactionWrappers(manualRedactions); - - for (RedactionLogEntry entry : redactionLog.getRedactionLogEntry()) { - var applicableManualRedactions = manualRedactionWrappers.stream().filter(mr -> entry.getId().equals(mr.getId())).collect(Collectors.toList()); - if (!applicableManualRedactions.isEmpty()) { - processRedactionLogEntry(applicableManualRedactions, entry, types, colors); - } - - if (entry.isImported() && !entry.isRedacted()) { - skippedImportedRedactions.add(entry.getId()); - } - - entry.setComments(convert(manualRedactions.getComments().get(entry.getId()))); - - if (excludedPages != null && !excludedPages.isEmpty()) { - entry.getPositions().forEach(pos -> { - if (!isLocalManualRedaction(entry) && excludedPages.contains(pos.getPage())) { - entry.setExcluded(true); - } - }); - } - } - - } - - Set processedIds = new HashSet<>(); - redactionLog.getRedactionLogEntry().removeIf(entry -> { - - if (entry.getImportedRedactionIntersections() != null) { - entry.getImportedRedactionIntersections().removeAll(skippedImportedRedactions); - if (!entry.getImportedRedactionIntersections().isEmpty() && (!entry.isImage() || entry.isImage() && !(entry.getType().equals("image") || entry.getType() - .equals("ocr")))) { - return true; - } - } - if (processedIds.contains(entry.getId())) { - log.info("Duplicate annotation found with id {}", entry.getId()); - return true; - } - processedIds.add(entry.getId()); - return false; - }); - return redactionLog; - } - - - private boolean isLocalManualRedaction(RedactionLogEntry entry) { - - return entry.getManualChanges() != null && entry.getManualChanges() - .stream() - .anyMatch(mc -> mc.getManualRedactionType() == ManualRedactionType.ADD_LOCALLY || mc.getManualRedactionType() == ManualRedactionType.RESIZE && mc.getAnnotationStatus() == AnnotationStatus.APPROVED && entry.getEngines() - .contains(Engine.RULE) && !entry.getEngines().contains(Engine.DICTIONARY)); - - } - - - private List createManualRedactionWrappers(ManualRedactions manualRedactions) { - - List manualRedactionWrappers = new ArrayList<>(); - - manualRedactions.getImageRecategorization().forEach(item -> { - if (item.getSoftDeletedTime() == null) { - manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)); - } - }); - - manualRedactions.getIdsToRemove().forEach(item -> { - if (item.getSoftDeletedTime() == null) { - manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)); - } - }); - - manualRedactions.getForceRedactions().forEach(item -> { - if (item.getSoftDeletedTime() == null) { - manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)); - } - }); - - manualRedactions.getLegalBasisChanges().forEach(item -> { - if (item.getSoftDeletedTime() == null) { - manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)); - } - }); - - manualRedactions.getResizeRedactions().forEach(item -> { - if (item.getSoftDeletedTime() == null) { - manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)); - } - }); - - Collections.sort(manualRedactionWrappers); - - return manualRedactionWrappers; - } - - - private void processRedactionLogEntry(List manualRedactionWrappers, RedactionLogEntry redactionLogEntry, List types, Colors colors) { - - manualRedactionWrappers.forEach(mrw -> { - - Object item = mrw.getItem(); - if (item instanceof ManualImageRecategorization) { - var imageRecategorization = (ManualImageRecategorization) item; - processManualRecategorization(redactionLogEntry, types, colors, imageRecategorization); - } - - if (item instanceof IdRemoval) { - var manualRemoval = (IdRemoval) item; - processIdRemoval(redactionLogEntry, types, colors, manualRemoval); - } - - if (item instanceof ManualForceRedaction) { - var manualForceRedact = (ManualForceRedaction) item; - processManualForceRedaction(redactionLogEntry, types, colors, manualForceRedact); - } - - if (item instanceof ManualLegalBasisChange) { - var manualLegalBasisChange = (ManualLegalBasisChange) item; - processManualLegalBasisChange(redactionLogEntry, types, colors, manualLegalBasisChange); - } - - if (item instanceof ManualResizeRedaction) { - var manualResizeRedact = (ManualResizeRedaction) item; - processManualResizeRedaction(redactionLogEntry, types, colors, manualResizeRedact); - } - - }); - - } - - - private void processManualRecategorization(RedactionLogEntry redactionLogEntry, List types, Colors colors, ManualImageRecategorization imageRecategorization) { - - String manualOverrideReason = null; - if (imageRecategorization.getStatus().equals(AnnotationStatus.APPROVED)) { - - redactionLogEntry.setType(imageRecategorization.getType()); - - if (isHint(types, imageRecategorization.getType())) { - redactionLogEntry.setRedacted(false); - redactionLogEntry.setHint(true); - } else { - redactionLogEntry.setHint(false); - } - - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", recategorized by manual override"); - } else if (imageRecategorization.getStatus().equals(AnnotationStatus.REQUESTED)) { - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to recategorize"); - redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types)); - } - - if (manualOverrideReason != null) { - redactionLogEntry.setReason(manualOverrideReason); - } - - redactionLogEntry.getManualChanges() - .add(ManualChange.from(imageRecategorization).withManualRedactionType(ManualRedactionType.RECATEGORIZE).withChange("type", imageRecategorization.getType())); - } - - - private String mergeReasonIfNecessary(String currentReason, String addition) { - - if (currentReason != null) { - if (!currentReason.contains(addition)) { - return currentReason + addition; - } - return currentReason; - } else { - return ""; - } - } - - - private void processIdRemoval(RedactionLogEntry redactionLogEntry, List types, Colors colors, IdRemoval manualRemoval) { - - boolean isApprovedRedaction = manualRemoval.getStatus().equals(AnnotationStatus.APPROVED); - if (isApprovedRedaction && manualRemoval.isRemoveFromDictionary() && isBasedOnDictionaryOnly(redactionLogEntry)) { - log.debug("Skipping merge for dictionary-modifying entry"); - } else { - String redactionLogEntryType = redactionLogEntry.getType(); - String manualOverrideReason = null; - if (isApprovedRedaction) { - redactionLogEntry.setRedacted(false); - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", removed by manual override"); - redactionLogEntry.setColor(getColor(redactionLogEntryType, colors, false, redactionLogEntry.isRedacted(), true, types)); - redactionLogEntry.setHint(false); - } else if (manualRemoval.getStatus().equals(AnnotationStatus.REQUESTED)) { - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to remove"); - redactionLogEntry.setColor(getColor(redactionLogEntryType, colors, true, redactionLogEntry.isRedacted(), false, types)); - } - - if (manualOverrideReason != null) { - redactionLogEntry.setReason(manualOverrideReason); - } - } - - redactionLogEntry.getManualChanges() - .add(ManualChange.from(manualRemoval) - .withManualRedactionType(manualRemoval.isRemoveFromDictionary() ? ManualRedactionType.REMOVE_FROM_DICTIONARY : ManualRedactionType.REMOVE_LOCALLY)); - } - - - private boolean isBasedOnDictionaryOnly(RedactionLogEntry redactionLogEntry) { - - return redactionLogEntry.getEngines().contains(Engine.DICTIONARY) && redactionLogEntry.getEngines().size() == 1; - } - - - private void processManualForceRedaction(RedactionLogEntry redactionLogEntry, List types, Colors colors, ManualForceRedaction manualForceRedact) { - - String manualOverrideReason = null; - var dictionaryIsHint = isHint(types, redactionLogEntry.getType()); - if (manualForceRedact.getStatus().equals(AnnotationStatus.APPROVED)) { - // Forcing a skipped hint should result in a hint - if (dictionaryIsHint) { - redactionLogEntry.setHint(true); - } else { - redactionLogEntry.setRedacted(true); - } - redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types)); - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", forced by manual override"); - redactionLogEntry.setLegalBasis(manualForceRedact.getLegalBasis()); - } else if (manualForceRedact.getStatus().equals(AnnotationStatus.REQUESTED)) { - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to force " + (dictionaryIsHint ? "hint" : "redact")); - redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types)); - redactionLogEntry.setLegalBasis(manualForceRedact.getLegalBasis()); - } - - if (manualOverrideReason != null) { - redactionLogEntry.setReason(manualOverrideReason); - } - - var manualChange = ManualChange.from(manualForceRedact).withManualRedactionType(dictionaryIsHint ? ManualRedactionType.FORCE_HINT : ManualRedactionType.FORCE_REDACT); - - redactionLogEntry.getManualChanges().add(manualChange); - } - - - private void processManualLegalBasisChange(RedactionLogEntry redactionLogEntry, List types, Colors colors, ManualLegalBasisChange manualLegalBasisChange) { - - String manualOverrideReason = null; - if (manualLegalBasisChange.getStatus().equals(AnnotationStatus.APPROVED)) { - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", legal basis was manually changed"); - redactionLogEntry.setLegalBasis(manualLegalBasisChange.getLegalBasis()); - redactionLogEntry.setRedacted(true); - if (manualLegalBasisChange.getSection() != null) { - redactionLogEntry.setSection(manualLegalBasisChange.getSection()); - } - if (redactionLogEntry.isRectangle() && manualLegalBasisChange.getValue() != null) { - redactionLogEntry.setValue(manualLegalBasisChange.getValue()); - } - } else if (manualLegalBasisChange.getStatus().equals(AnnotationStatus.REQUESTED)) { - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", legal basis change requested"); - redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types)); - } - - if (manualOverrideReason != null) { - redactionLogEntry.setReason(manualOverrideReason); - } - - var manualChange = ManualChange.from(manualLegalBasisChange).withManualRedactionType(ManualRedactionType.LEGAL_BASIS_CHANGE); - manualChange.withChange("legalBasis", manualLegalBasisChange.getLegalBasis()); - if (manualLegalBasisChange.getSection() != null) { - manualChange.withChange("section", manualLegalBasisChange.getSection()); - } - if (redactionLogEntry.isRectangle() && manualLegalBasisChange.getValue() != null) { - manualChange.withChange("value", manualLegalBasisChange.getValue()); - } - redactionLogEntry.getManualChanges().add(manualChange); - } - - - private void processManualResizeRedaction(RedactionLogEntry redactionLogEntry, List types, Colors colors, ManualResizeRedaction manualResizeRedact) { - - String manualOverrideReason = null; - if (manualResizeRedact.getStatus().equals(AnnotationStatus.APPROVED)) { - redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types)); - redactionLogEntry.setPositions(convertPositions(manualResizeRedact.getPositions())); - if (!"signature".equalsIgnoreCase(redactionLogEntry.getType()) && !"logo".equalsIgnoreCase(redactionLogEntry.getType())) { - redactionLogEntry.setValue(manualResizeRedact.getValue()); - } - // This is for backwards compatibility, now the text after/before is calculated during reanalysis because we need to find dict entries on positions where entries are resized to smaller. - if (manualResizeRedact.getTextBefore() != null || manualResizeRedact.getTextAfter() != null) { - redactionLogEntry.setTextBefore(manualResizeRedact.getTextBefore()); - redactionLogEntry.setTextAfter(manualResizeRedact.getTextAfter()); - } - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", resized by manual override"); - } else if (manualResizeRedact.getStatus().equals(AnnotationStatus.REQUESTED)) { - manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to resize redact"); - redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types)); - redactionLogEntry.setPositions(convertPositions(manualResizeRedact.getPositions())); - - // This is for backwards compatibility, now the text after/before is calculated during reanalysis because we need to find dict entries on positions where entries are resized to smaller. - if (manualResizeRedact.getTextBefore() != null || manualResizeRedact.getTextAfter() != null) { - redactionLogEntry.setTextBefore(manualResizeRedact.getTextBefore()); - redactionLogEntry.setTextAfter(manualResizeRedact.getTextAfter()); - } - } - - redactionLogEntry.setReason(manualOverrideReason); - redactionLogEntry.getManualChanges() - .add(ManualChange.from(manualResizeRedact).withManualRedactionType(ManualRedactionType.RESIZE).withChange("value", manualResizeRedact.getValue())); - } - - - public List addManualAddEntries(SectionGrid sectionGrid, - Set manualAdds, - Map> comments, - Colors colors, - List types, - int analysisNumber) { - - List redactionLogEntries = new ArrayList<>(); - - for (ManualRedactionEntry manualRedactionEntry : manualAdds) { - - if (shouldCreateManualEntry(manualRedactionEntry)) { - RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry, manualRedactionEntry.getAnnotationId(), colors, types, analysisNumber); - redactionLogEntry.setPositions(convertPositions(manualRedactionEntry.getPositions())); - redactionLogEntry.setComments(convert(comments.get(manualRedactionEntry.getAnnotationId()))); - redactionLogEntry.setTextBefore(manualRedactionEntry.getTextBefore()); - redactionLogEntry.setTextAfter(manualRedactionEntry.getTextAfter()); - - sectionTextService.handleSectionText(sectionGrid, redactionLogEntry); - - redactionLogEntries.add(redactionLogEntry); - } - } - - return redactionLogEntries; - } - - - private List convert(List comments) { - - return comments == null ? null : comments.stream() - .map(c -> new RedactionLogComment(c.getId(), c.getUser(), c.getText(), c.getAnnotationId(), c.getFileId(), c.getDate(), c.getSoftDeletedTime())) - .collect(Collectors.toList()); - } - - - private List convertPositions(List positions) { - - return positions.stream() - .map(pos -> new Rectangle(new Point(pos.getTopLeftX(), pos.getTopLeftY()), pos.getWidth(), pos.getHeight(), pos.getPage())) - .collect(Collectors.toList()); - } - - - @SuppressWarnings("PMD.UselessParentheses") - private boolean shouldCreateManualEntry(ManualRedactionEntry manualRedactionEntry) { - - return (!manualRedactionEntry.isAddToDictionary() && !manualRedactionEntry.isAddToDossierDictionary()) || ((manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary()) && manualRedactionEntry.getProcessedDate() == null); - } - - - private RedactionLogEntry createRedactionLogEntry(ManualRedactionEntry manualRedactionEntry, String id, Colors colors, List types, int analysisNumber) { - - var addToDictionary = manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary(); - - var change = ManualChange.from(manualRedactionEntry).withManualRedactionType(addToDictionary ? ManualRedactionType.ADD_TO_DICTIONARY : ManualRedactionType.ADD_LOCALLY); - List changeList = new ArrayList<>(); - changeList.add(change); - - return RedactionLogEntry.builder() - .id(id) - .color(getColorForManualAdd(manualRedactionEntry.getType(), colors, manualRedactionEntry.getStatus(), types)) - .reason(manualRedactionEntry.getReason()) - .isDictionaryEntry(manualRedactionEntry.isAddToDictionary()) - .isDossierDictionaryEntry(manualRedactionEntry.isAddToDossierDictionary()) - .legalBasis(manualRedactionEntry.getLegalBasis()) - .value(manualRedactionEntry.getValue()) - .sourceId(manualRedactionEntry.getSourceId()) - .section(manualRedactionEntry.getSection()) - .type(manualRedactionEntry.getType()) - .redacted(true) - .isHint(false) - .sectionNumber(-1) - .rectangle(manualRedactionEntry.isRectangle()) - .manualChanges(changeList) - .changes(List.of(new Change(analysisNumber + 1, ChangeType.ADDED, manualRedactionEntry.getRequestDate()))) - .build(); - } - - - private float[] getColor(String type, Colors colors, boolean requested, boolean isRedaction, boolean skipped, List types) { - - if (requested) { - return ColorUtils.convertColor(colors.getRequestRemoveColor()); - } - if (skipped || !isRedaction && !isHint(types, type)) { - return ColorUtils.convertColor(colors.getSkippedColor()); - } - return getColor(types, type); - } - - - private float[] getColorForManualAdd(String type, Colors colors, AnnotationStatus status, List types) { - - if (status.equals(AnnotationStatus.REQUESTED)) { - return ColorUtils.convertColor(colors.getRequestAddColor()); - } else if (status.equals(AnnotationStatus.DECLINED)) { - return ColorUtils.convertColor(colors.getSkippedColor()); - } - return getColor(types, type); - } - - - private float[] getColor(List types, String type) { - - var matchingTypes = getMatchingTypes(types, type); - Optional foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst(); - if (foundAndNotDeletedType.isPresent()) { - return ColorUtils.convertColor(foundAndNotDeletedType.get().getHexColor()); - } - Optional firstDeletedType = matchingTypes.stream().findFirst(); - return firstDeletedType.map(value -> ColorUtils.convertColor(value.getHexColor())).orElseGet(() -> ColorUtils.convertColor(DELETED_TYPE_COLOR)); - } - - - boolean isHint(List types, String type) { - - var matchingTypes = getMatchingTypes(types, type); - Optional foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst(); - if (foundAndNotDeletedType.isPresent()) { - return foundAndNotDeletedType.get().isHint(); - } - Optional firstDeletedType = matchingTypes.stream().findFirst(); - return firstDeletedType.map(Type::isHint).orElse(false); - } - - - private List getMatchingTypes(List types, String type) { - - return types.stream().filter(t -> t.getType().equals(type)).collect(Collectors.toList()); - } - - - private boolean isDeletedType(Type type) { - - return type.getSoftDeletedTime() != null; - } - - - @Data - @AllArgsConstructor - private static class ManualRedactionWrapper implements Comparable { - - private String id; - private OffsetDateTime date; - private Object item; - - - @Override - public int compareTo(ManualRedactionWrapper o) { - - return this.date.compareTo(o.date); - } - - } - -} - diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml index cfbcaf2eb..f1736673a 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/db.changelog-tenant.yaml @@ -155,4 +155,6 @@ databaseChangeLog: file: db/changelog/tenant/106-add-add-to-all-dossiers-to-resize-redactions.yaml - include: file: db/changelog/tenant/107-add-last-layout-processed-column.yaml + - include: + file: db/changelog/tenant/108-added-dictionary-changes-to-manual-recategorization.yaml diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/tenant/108-added-dictionary-changes-to-manual-recategorization.yaml b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/tenant/108-added-dictionary-changes-to-manual-recategorization.yaml new file mode 100644 index 000000000..ec01ac15f --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/resources/db/changelog/tenant/108-added-dictionary-changes-to-manual-recategorization.yaml @@ -0,0 +1,79 @@ +databaseChangeLog: + - changeSet: + id: added-dictionary-changes-to-manual-recategorization + author: kilian + changes: + - addColumn: + columns: + - column: + name: add_to_dictionary + type: BOOLEAN + tableName: manual_image_recategorization + - addColumn: + columns: + - column: + name: add_to_all_dossiers + type: BOOLEAN + tableName: manual_image_recategorization + - changeSet: + id: added-dictionary-changes-to-manual-recategorization-2 + author: kilian + changes: + - createTable: + columns: + - column: + constraints: + nullable: false + name: manual_image_recategorization_entity_annotation_id + type: VARCHAR(255) + - column: + constraints: + nullable: false + name: manual_image_recategorization_entity_file_id + type: VARCHAR(255) + - column: + name: type_ids_of_dictionaries_with_add + type: VARCHAR(255) + tableName: manual_recategorization_type_ids_of_dictionaries_with_add + - createTable: + columns: + - column: + constraints: + nullable: false + name: manual_image_recategorization_entity_annotation_id + type: VARCHAR(255) + - column: + constraints: + nullable: false + name: manual_image_recategorization_entity_file_id + type: VARCHAR(255) + - column: + name: type_ids_of_dictionaries_with_delete + type: VARCHAR(255) + tableName: manual_recategorization_type_ids_of_dictionaries_with_delete + - changeSet: + id: added-dictionary-changes-to-manual-recategorization-3 + author: kilian + changes: + - addForeignKeyConstraint: + baseColumnNames: manual_image_recategorization_entity_annotation_id, manual_image_recategorization_entity_file_id + baseTableName: manual_recategorization_type_ids_of_dictionaries_with_add + constraintName: fk_manual_recategorization_to_dictionary_add + deferrable: false + initiallyDeferred: false + onDelete: NO ACTION + onUpdate: NO ACTION + referencedColumnNames: annotation_id, file_id + referencedTableName: manual_image_recategorization + validate: true + - addForeignKeyConstraint: + baseColumnNames: manual_image_recategorization_entity_annotation_id, manual_image_recategorization_entity_file_id + baseTableName: manual_recategorization_type_ids_of_dictionaries_with_delete + constraintName: fk_manual_recategorization_to_dictionary_delete + deferrable: false + initiallyDeferred: false + onDelete: NO ACTION + onUpdate: NO ACTION + referencedColumnNames: annotation_id, file_id + referencedTableName: manual_image_recategorization + validate: true \ No newline at end of file diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/ManualRedactionTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/ManualRedactionTest.java index e6f9696fb..73a74774b 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/ManualRedactionTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/ManualRedactionTest.java @@ -105,7 +105,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.REDACTION_LOG, redactionLog); - when(redactionLogMergeService.provideRedactionLog(Mockito.any())).thenReturn(redactionLog); + when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(redactionLog); Assertions.assertThrows(FeignException.Forbidden.class, () -> manualRedactionClient.removeRedactionBulk(dossier.getId(), @@ -254,7 +254,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.REDACTION_LOG, redactionLog); - when(redactionLogMergeService.provideRedactionLog(Mockito.any())).thenReturn(redactionLog); + when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(redactionLog); manualRedactionClient.removeRedactionBulk(dossier.getId(), file.getId(), @@ -302,7 +302,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.REDACTION_LOG, redactionLog); - when(redactionLogMergeService.provideRedactionLog(Mockito.any())).thenReturn(redactionLog); + when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(redactionLog); manualRedactionClient.removeRedactionBulk(dossier.getId(), file.getId(), @@ -343,7 +343,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.REDACTION_LOG, redactionLog); - when(redactionLogMergeService.provideRedactionLog(Mockito.any())).thenReturn(redactionLog); + when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(redactionLog); manualRedactionClient.removeRedactionBulk(dossier.getId(), file.getId(), @@ -427,8 +427,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.REDACTION_LOG, redactionLog1); var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest1)).thenReturn(redactionLog1); - when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId(), true, true)).thenReturn(redactionLog1); + when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId())).thenReturn(redactionLog1); var redactionLog2 = new RedactionLog(1, 1, @@ -445,8 +444,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.REDACTION_LOG, redactionLog2); var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest2)).thenReturn(redactionLog2); - when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId(), true, true)).thenReturn(redactionLog2); + when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId())).thenReturn(redactionLog2); // resize redaction in dossier 1 var resizeRedactionDosAndAddToAllDos = ResizeRedactionRequestModel.builder() @@ -576,8 +574,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.REDACTION_LOG, redactionLog1); var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest1)).thenReturn(redactionLog1); - when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId(), true, true)).thenReturn(redactionLog1); + when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId())).thenReturn(redactionLog1); var redactionLog2 = new RedactionLog(1, 1, @@ -594,8 +591,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.REDACTION_LOG, redactionLog2); var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest2)).thenReturn(redactionLog2); - when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId(), true, true)).thenReturn(redactionLog2); + when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId())).thenReturn(redactionLog2); // resize redaction in dossier 1 var resizeRedactionDosAndAddToAllDos = ResizeRedactionRequestModel.builder() @@ -727,8 +723,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.REDACTION_LOG, redactionLog1); var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest1)).thenReturn(redactionLog1); - when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId(), true, true)).thenReturn(redactionLog1); + when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId())).thenReturn(redactionLog1); var redactionLog2 = new RedactionLog(1, 1, @@ -745,8 +740,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.REDACTION_LOG, redactionLog2); var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest2)).thenReturn(redactionLog2); - when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId(), true, true)).thenReturn(redactionLog2); + when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId())).thenReturn(redactionLog2); // resize redaction in dossier dict var resizeRedactionDosTemp = ResizeRedactionRequestModel.builder() @@ -875,8 +869,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier1.getId(), file1.getId(), FileType.REDACTION_LOG, redactionLog1); var redactionRequest1 = RedactionRequest.builder().dossierId(file1.getDossierId()).fileId(file1.getFileId()).dossierTemplateId(file1.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest1)).thenReturn(redactionLog1); - when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId(), true, true)).thenReturn(redactionLog1); + when(redactionLogService.getRedactionLog(file1.getDossierId(), file1.getFileId())).thenReturn(redactionLog1); var redactionLog2 = new RedactionLog(1, 1, @@ -893,8 +886,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest { 0); fileManagementStorageService.storeJSONObject(dossier2.getId(), file2.getId(), FileType.REDACTION_LOG, redactionLog2); var redactionRequest2 = RedactionRequest.builder().dossierId(file2.getDossierId()).fileId(file2.getFileId()).dossierTemplateId(file2.getDossierTemplateId()).build(); - when(redactionLogMergeService.provideRedactionLog(redactionRequest2)).thenReturn(redactionLog2); - when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId(), true, true)).thenReturn(redactionLog2); + when(redactionLogService.getRedactionLog(file2.getDossierId(), file2.getFileId())).thenReturn(redactionLog2); // resize redaction in dossier dict var resizeRedactionDosTemp = ResizeRedactionRequestModel.builder() diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RedactionLogTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RedactionLogTest.java deleted file mode 100644 index 9a4977fd1..000000000 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/tests/RedactionLogTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.iqser.red.service.peristence.v1.server.integration.tests; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.OffsetDateTime; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import com.iqser.red.service.peristence.v1.server.integration.client.RedactionLogClient; -import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider; -import com.iqser.red.service.peristence.v1.server.integration.service.FileTesterAndProvider; -import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest; -import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.FilteredRedactionLogRequest; - -public class RedactionLogTest extends AbstractPersistenceServerServiceTest { - - @Autowired - private FileTesterAndProvider fileTesterAndProvider; - - @Autowired - private DossierTesterAndProvider dossierTesterAndProvider; - - @Autowired - private RedactionLogClient redactionLogClient; - - - @Test - public void testRedactionLog() { - - var dossier = dossierTesterAndProvider.provideTestDossier(); - var file = fileTesterAndProvider.testAndProvideFile(dossier); - - assertThat(redactionLogClient.getSectionGrid(dossier.getId(), file.getId())).isNotNull(); - assertThat(redactionLogClient.getRedactionLog(dossier.getId(), file.getId(), null, true, false)).isNotNull(); - assertThat(redactionLogClient.getRedactionLog(dossier.getId(), file.getId(), null, false, false)).isNotNull(); - assertThat(redactionLogClient.getFilteredRedactionLog(dossier.getId(), - file.getId(), - FilteredRedactionLogRequest.builder() - .excludedTypes(null) - .withManualRedactions(true) - .includeFalsePositives(false) - .specifiedDate(OffsetDateTime.now().minusDays(30)) - .build())).isNotNull(); - } - -} diff --git a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java index b63d0438d..4c975313d 100644 --- a/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java +++ b/persistence-service-v1/persistence-service-server-v1/src/test/java/com/iqser/red/service/peristence/v1/server/integration/utils/AbstractPersistenceServerServiceTest.java @@ -57,6 +57,7 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.configur 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.FileManagementStorageService; +import com.iqser.red.service.persistence.management.v1.processor.service.RedactionLogService; 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; @@ -87,7 +88,6 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalsePositiveEntryRepository; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.FalseRecommendationEntryRepository; -import com.iqser.red.service.persistence.management.v1.processor.service.redactionlog.RedactionLogMergeService; import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService; import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.ApplicationConfig; import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog; @@ -123,7 +123,7 @@ public abstract class AbstractPersistenceServerServiceTest { @MockBean protected SearchClient searchClient; @MockBean - protected RedactionLogMergeService redactionLogMergeService; + protected RedactionLogService redactionLogService; @MockBean protected PDFTronClient pdfTronRedactionClient; @Autowired @@ -277,7 +277,7 @@ public abstract class AbstractPersistenceServerServiceTest { // doNothing().when(pdfTronRedactionClient).testDigitalCurrentSignature(Mockito.any()); when(amqpAdmin.getQueueInfo(Mockito.any())).thenReturn(null); - when(redactionLogMergeService.provideRedactionLog(Mockito.any())).thenReturn(new RedactionLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0)); + when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(new RedactionLog(1, 1, Lists.newArrayList(), null, 0, 0, 0, 0)); when(redactionClient.testRules(Mockito.anyString())).thenReturn(DroolsSyntaxValidation.builder().compiled(true).droolsSyntaxErrorMessages(Collections.emptyList()).build()); } diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/AddRedactionRequest.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/AddRedactionRequest.java index 7ee98a327..e720bbc24 100644 --- a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/AddRedactionRequest.java +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/AddRedactionRequest.java @@ -14,7 +14,7 @@ import lombok.NoArgsConstructor; @Builder @AllArgsConstructor @NoArgsConstructor -public class AddRedactionRequest implements ManualRequestWithDictionary { +public class AddRedactionRequest implements ManualRequestWithAddToDictionary { private String user; private String dossierTemplateTypeId; diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithDictionary.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithAddToDictionary.java similarity index 79% rename from persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithDictionary.java rename to persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithAddToDictionary.java index 476f23113..200276fa5 100644 --- a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithDictionary.java +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithAddToDictionary.java @@ -2,9 +2,10 @@ package com.iqser.red.service.persistence.service.v1.api.shared.model.annotation import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType; -public interface ManualRequestWithDictionary extends BaseManualRequest{ +public interface ManualRequestWithAddToDictionary extends BaseManualRequest { String getValue(); + String getDossierId(); boolean isAddToDictionary(); boolean isAddToAllDossiers(); boolean isForceAddToDictionary(); diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithRemoveFromDictionary.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithRemoveFromDictionary.java new file mode 100644 index 000000000..b04a26347 --- /dev/null +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/ManualRequestWithRemoveFromDictionary.java @@ -0,0 +1,13 @@ +package com.iqser.red.service.persistence.service.v1.api.shared.model.annotations; + +public interface ManualRequestWithRemoveFromDictionary extends BaseManualRequest { + + String getAnnotationId(); + + + boolean isRemoveFromDictionary(); + + + boolean isRemoveFromAllDossiers(); + +} diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RecategorizationRequest.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RecategorizationRequest.java index 43d234829..269047687 100644 --- a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RecategorizationRequest.java +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RecategorizationRequest.java @@ -14,12 +14,13 @@ import lombok.experimental.FieldDefaults; @AllArgsConstructor @NoArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) -public class RecategorizationRequest implements ManualRequestWithDictionary { +public class RecategorizationRequest implements ManualRequestWithAddToDictionary, ManualRequestWithRemoveFromDictionary { String annotationId; String user; AnnotationStatus status; - String typeId; + String dossierTemplateTypeId; + String dossierId; String comment; int page; String value; @@ -36,9 +37,16 @@ public class RecategorizationRequest implements ManualRequestWithDictionary { @Override - public String getDossierTemplateTypeId() { + public boolean isRemoveFromDictionary() { - return typeId; + return addToDictionary; + } + + + @Override + public boolean isRemoveFromAllDossiers() { + + return addToAllDossiers; } } diff --git a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RemoveRedactionRequest.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RemoveRedactionRequest.java index fbdf4b014..0dae857d2 100644 --- a/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RemoveRedactionRequest.java +++ b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/RemoveRedactionRequest.java @@ -9,7 +9,7 @@ import lombok.NoArgsConstructor; @Builder @AllArgsConstructor @NoArgsConstructor -public class RemoveRedactionRequest implements BaseManualRequest{ +public class RemoveRedactionRequest implements ManualRequestWithRemoveFromDictionary { private String annotationId; private String user;