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 b8a575214..4ce41002b 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 @@ -8,8 +8,6 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac import static com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils.toTypeId; import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -20,10 +18,10 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService; import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService; import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionService; +import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionUndoService; import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService; import com.iqser.red.service.persistence.service.v1.api.external.resource.ManualRedactionResource; import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory; @@ -31,20 +29,14 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.CommentResp import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AddRedactionRequest; 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.CommentRequest; +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.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.audit.AuditRequest; 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; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequestModel; -import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ManualRedactionWrapperModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RecategorizationRequestModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequestModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequestModel; @@ -62,182 +54,20 @@ public class ManualRedactionController implements ManualRedactionResource { private static final String DOSSIER_ID = "dossierId"; private static final String ANNOTATION_ID = "annotationId"; private final ManualRedactionService manualRedactionService; + private final ManualRedactionUndoService manualRedactionUndoService; private final DossierManagementService dossierManagementService; private final AuditPersistenceService auditPersistenceService; private final AccessControlService accessControlService; - private ManualRedactionWrapperModel getLatestManualRedactionForAnnotationId(ManualRedactions manualRedactions, String annotationId) { - - final List manualRedactionWrappers = new ArrayList<>(); - - manualRedactions.getEntriesToAdd() - .stream() - .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) - .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); - - manualRedactions.getImageRecategorization() - .stream() - .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) - .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); - - manualRedactions.getIdsToRemove() - .stream() - .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) - .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); - - manualRedactions.getForceRedactions() - .stream() - .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) - .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); - - manualRedactions.getLegalBasisChanges() - .stream() - .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) - .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); - - manualRedactions.getResizeRedactions() - .stream() - .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) - .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); - - var sortedManualRedactionWrappers = manualRedactionWrappers.stream() - .sorted(Comparator.comparing(ManualRedactionWrapperModel::getDate, Comparator.nullsLast(Comparator.reverseOrder()))) - .collect(Collectors.toList()); - - return sortedManualRedactionWrappers.isEmpty() ? null : sortedManualRedactionWrappers.get(0); - } - - @Override @PreAuthorize("hasAuthority('" + DELETE_MANUAL_REDACTION + "')") public void undo(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody Set annotationIds) { accessControlService.verifyFileIsNotApproved(dossierId, fileId); accessControlService.verifyUserIsApprover(dossierId); + manualRedactionUndoService.undo(dossierId, fileId, annotationIds); - ManualRedactions manualRedactions = manualRedactionService.getManualRedactions(fileId); - - Map manualRedactionWrappers = getLatestManualRedactionsForAnnotationIds(manualRedactions, annotationIds); - - if (manualRedactionWrappers.isEmpty()) { - throw new NotFoundException(String.format("ManualRedaction with annotationIds %s could not be found.", annotationIds)); - } - - List manualRedactionEntries = manualRedactionWrappers.values() - .stream() - .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualRedactionEntry) - .map(ManualRedactionWrapperModel::getId) - .collect(Collectors.toList()); - if (!manualRedactionEntries.isEmpty()) { - manualRedactionService.deleteAddRedaction(dossierId, fileId, manualRedactionEntries); - manualRedactionEntries.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() - .userId(KeycloakSecurity.getUserId()) - .objectId(fileId) - .category(AuditCategory.DOCUMENT.name()) - .message("Undo of manual add redaction was done.") - .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) - .build())); - } - - List idRemovals = manualRedactionWrappers.values() - .stream() - .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof IdRemoval) - .map(ManualRedactionWrapperModel::getId) - .collect(Collectors.toList()); - if (!idRemovals.isEmpty()) { - - manualRedactionService.deleteRemoveRedaction(dossierId, fileId, idRemovals); - idRemovals.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() - .userId(KeycloakSecurity.getUserId()) - .objectId(fileId) - .category(AuditCategory.DOCUMENT.name()) - .message("Undo of manual remove redaction was done.") - .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) - .build())); - } - - List manualForceRedactions = manualRedactionWrappers.values() - .stream() - .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualForceRedaction) - .map(ManualRedactionWrapperModel::getId) - .collect(Collectors.toList()); - if (!manualForceRedactions.isEmpty()) { - - manualRedactionService.deleteForceRedaction(dossierId, fileId, manualForceRedactions); - manualForceRedactions.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() - .userId(KeycloakSecurity.getUserId()) - .objectId(fileId) - .category(AuditCategory.DOCUMENT.name()) - .message("Undo of manual force redaction was done.") - .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) - .build())); - } - - List manualImageRecategorizations = manualRedactionWrappers.values() - .stream() - .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualImageRecategorization) - .map(ManualRedactionWrapperModel::getId) - .collect(Collectors.toList()); - if (!manualImageRecategorizations.isEmpty()) { - - manualRedactionService.deleteImageRecategorization(dossierId, fileId, manualImageRecategorizations); - manualImageRecategorizations.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() - .userId(KeycloakSecurity.getUserId()) - .objectId(fileId) - .category(AuditCategory.DOCUMENT.name()) - .message("Undo of manual image recategorization was done.") - .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) - .build())); - } - - List manualLegalBasisChanges = manualRedactionWrappers.values() - .stream() - .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualLegalBasisChange) - .map(ManualRedactionWrapperModel::getId) - .collect(Collectors.toList()); - if (!manualLegalBasisChanges.isEmpty()) { - - manualRedactionService.deleteLegalBasisChange(dossierId, fileId, manualLegalBasisChanges); - manualLegalBasisChanges.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() - .userId(KeycloakSecurity.getUserId()) - .objectId(fileId) - .category(AuditCategory.DOCUMENT.name()) - .message("Undo of legal basis change was done.") - .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) - .build())); - } - - List manualResizeRedactions = manualRedactionWrappers.values() - .stream() - .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualResizeRedaction) - .map(ManualRedactionWrapperModel::getId) - .collect(Collectors.toList()); - if (!manualResizeRedactions.isEmpty()) { - - manualRedactionService.deleteResizeRedaction(dossierId, fileId, manualResizeRedactions); - manualResizeRedactions.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() - .userId(KeycloakSecurity.getUserId()) - .objectId(fileId) - .category(AuditCategory.DOCUMENT.name()) - .message("Undo of manual resize redaction was done.") - .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) - .build())); - } - } - - - private Map getLatestManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set annotationIds) { - - Map result = new HashMap<>(); - annotationIds.forEach(annotationId -> { - var last = getLatestManualRedactionForAnnotationId(manualRedactions, annotationId); - if (last != null) { - result.put(annotationId, last); - } - }); - - return result; } @@ -270,6 +100,13 @@ 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) { + + return manualRedactionService.getComments(fileId); + } + @Override @PreAuthorize("hasAuthority('" + ADD_COMMENT + "')") diff --git a/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/ManualRedactionResource.java b/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/ManualRedactionResource.java index 5cd1ff519..93904a9cd 100644 --- a/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/ManualRedactionResource.java +++ b/persistence-service-v1/persistence-service-external-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/external/resource/ManualRedactionResource.java @@ -13,13 +13,14 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseStatus; import com.iqser.red.service.persistence.service.v1.api.shared.model.CommentResponse; +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.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; -import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RecategorizationRequestModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequestModel; +import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RecategorizationRequestModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequestModel; import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequestModel; @@ -132,4 +133,12 @@ public interface ManualRedactionResource { @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) ManualRedactions getManualRedactions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId); + + + @ResponseStatus(value = HttpStatus.OK) + @GetMapping(value = MANUAL_REDACTION_REST_PATH + "/comments" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE) + @Operation(summary = "Returns the comments for a specific file", description = "None") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")}) + Comments getComments(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId); + } 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 01b69b8be..5310ed49c 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 @@ -9,7 +9,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -45,7 +47,9 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AddRedactionRequest; 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.BaseManualRequest; +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.CommentRequest; +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.ForceRedactionRequest; 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; @@ -55,6 +59,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations 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; @@ -104,16 +109,10 @@ public class ManualRedactionService { String annotationId = hashFunction.hashString(fileId + addRedactionRequest, StandardCharsets.UTF_8).toString(); - if (addRedactionRequest.isAddToDictionary()) { - Set typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleAddToDictionary(fileId, dossierId, addRedactionRequest); - addRedactionPersistenceService.updateStatus(fileId, annotationId, addRedactionRequest.getStatus(), true, typeIdsOfModifiedDictionaries); - } else { - addRedactionPersistenceService.updateStatus(fileId, annotationId, addRedactionRequest.getStatus(), false, null); - } - addRedactionPersistenceService.insert(fileId, annotationId, addRedactionRequest); + manualRedactionDictionaryUpdateHandler.handleAddToDictionary(fileId, annotationId, addRedactionRequest); - Long commentId = addComment(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser()).getId(); + Long commentId = addCommentAndGetId(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser()); response.add(ManualAddResponse.builder().annotationId(annotationId).commentId(commentId).build()); } @@ -160,6 +159,15 @@ public class ManualRedactionService { } + private Long addCommentAndGetId(String fileId, String annotationId, String comment, String user) { + + if (comment == null) { + return null; + } + return addComment(fileId, annotationId, comment, user).getId(); + } + + private CommentEntity addComment(String fileId, String annotationId, String comment, String user) { if (comment == null) { @@ -242,7 +250,7 @@ public class ManualRedactionService { log.info("add removeRedaction for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId()); IdRemoval idRemoval = MagicConverter.convert(removeRedactionPersistenceService.insert(fileId, removeRedactionRequest), IdRemoval.class); - Long commentId = addComment(fileId, removeRedactionRequest.getAnnotationId(), removeRedactionRequest.getComment(), removeRedactionRequest.getUser()).getId(); + Long commentId = addCommentAndGetId(fileId, removeRedactionRequest.getAnnotationId(), removeRedactionRequest.getComment(), removeRedactionRequest.getUser()); boolean matchingEntryFound = false; if (!removeRedactionRequest.isRemoveFromDictionary() && AnnotationStatus.APPROVED.equals(removeRedactionRequest.getStatus())) { try { @@ -255,7 +263,6 @@ public class ManualRedactionService { requiresReAnalysis = requiresReAnalysis || matchingEntryFound; } - boolean removedFromDictionary = handleRemoveFromDictionary(getRedactionLogEntry(redactionLog, removeRedactionRequest.getAnnotationId()), dossier, fileId, @@ -293,7 +300,7 @@ public class ManualRedactionService { if (!idRemoval.getStatus().equals(AnnotationStatus.APPROVED)) { return false; } - if (idRemoval.isRemoveFromDictionary()) { + if (!idRemoval.isRemoveFromDictionary()) { removeRedactionPersistenceService.updateStatus(fileId, annotationId, idRemoval.getStatus(), false, Collections.emptySet()); return false; } @@ -373,7 +380,7 @@ public class ManualRedactionService { for (var forceRedactionRequest : forceRedactionRequests) { forceRedactionPersistenceService.insert(fileId, forceRedactionRequest); - Long commentId = addComment(fileId, forceRedactionRequest.getAnnotationId(), forceRedactionRequest.getComment(), forceRedactionRequest.getUser()).getId(); + Long commentId = addCommentAndGetId(fileId, forceRedactionRequest.getAnnotationId(), forceRedactionRequest.getComment(), forceRedactionRequest.getUser()); requiresReanalysis = requiresReanalysis || forceRedactionRequest.isApproved(); response.add(ManualAddResponse.builder().annotationId(forceRedactionRequest.getAnnotationId()).commentId(commentId).build()); @@ -398,7 +405,7 @@ public class ManualRedactionService { for (var legalBasisChangeRequest : legalBasisChangeRequests) { legalBasisChangePersistenceService.insert(fileId, legalBasisChangeRequest); - Long commentId = addComment(fileId, legalBasisChangeRequest.getAnnotationId(), legalBasisChangeRequest.getComment(), legalBasisChangeRequest.getUser()).getId(); + Long commentId = addCommentAndGetId(fileId, legalBasisChangeRequest.getAnnotationId(), legalBasisChangeRequest.getComment(), legalBasisChangeRequest.getUser()); response.add(ManualAddResponse.builder().annotationId(legalBasisChangeRequest.getAnnotationId()).commentId(commentId).build()); } @@ -412,19 +419,19 @@ public class ManualRedactionService { public List addRecategorization(String dossierId, String fileId, List recategorizationRequests) { var response = new ArrayList(); - var actionPerformed = false; + var requiresReanalysis = false; dossierPersistenceService.getAndValidateDossier(dossierId); for (var recategorizationRequest : recategorizationRequests) { recategorizationPersistenceService.insert(fileId, recategorizationRequest); - Long commentId = addComment(fileId, recategorizationRequest.getAnnotationId(), recategorizationRequest.getComment(), recategorizationRequest.getUser()).getId(); + Long commentId = addCommentAndGetId(fileId, recategorizationRequest.getAnnotationId(), recategorizationRequest.getComment(), recategorizationRequest.getUser()); - actionPerformed = actionPerformed || recategorizationRequest.getStatus().equals(AnnotationStatus.APPROVED); + requiresReanalysis = requiresReanalysis || recategorizationRequest.getStatus().equals(AnnotationStatus.APPROVED); response.add(ManualAddResponse.builder().annotationId(recategorizationRequest.getAnnotationId()).commentId(commentId).build()); } - if (actionPerformed) { + if (requiresReanalysis) { reprocess(dossierId, fileId); } @@ -448,17 +455,19 @@ public class ManualRedactionService { public void deleteAddRedaction(String dossierId, String fileId, List annotationIds) { var dossier = dossierPersistenceService.getAndValidateDossier(dossierId); - var changedDictionary = false; + var requiresReanalysis = false; for (String annotationId : annotationIds) { + ManualRedactionEntryEntity addRedaction = getAddRedaction(fileId, annotationId); - changedDictionary = manualRedactionDictionaryUpdateHandler.revertAddToDictionary(fileId, dossier.getId(), addRedaction) || changedDictionary; + boolean dictionaryChanged = manualRedactionDictionaryUpdateHandler.revertAddToDictionary(fileId, dossier.getId(), addRedaction); + requiresReanalysis = requiresReanalysis || dictionaryChanged; addRedactionPersistenceService.updateStatus(fileId, annotationId, AnnotationStatus.APPROVED, false, null); addRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now()); } - if (changedDictionary) { + if (requiresReanalysis) { reprocess(dossierId, fileId); } @@ -471,7 +480,7 @@ public class ManualRedactionService { var dossier = dossierPersistenceService.getAndValidateDossier(dossierId); var requiresReanalysis = false; - RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId, true, true); + RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId); for (String annotationId : annotationIds) { @@ -522,38 +531,48 @@ public class ManualRedactionService { @Transactional - public void deleteImageRecategorization(String dossierId, String fileId, List annotationIds) { + public void deleteRecategorization(String dossierId, String fileId, List annotationIds) { - var actionPerformed = false; + var requiresReanalysis = false; for (var annotationId : annotationIds) { - var imageRecategorization = getImageRecategorization(fileId, annotationId); + var imageRecategorization = getRecategorization(fileId, annotationId); recategorizationPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now()); - actionPerformed = actionPerformed || !AnnotationStatus.REQUESTED.equals(imageRecategorization.getStatus()); + requiresReanalysis = requiresReanalysis || !AnnotationStatus.REQUESTED.equals(imageRecategorization.getStatus()); } - if (actionPerformed) { + if (requiresReanalysis) { reprocess(dossierId, fileId); } analysisFlagsCalculationService.calculateFlags(dossierId, fileId); } - private ManualImageRecategorizationEntity getImageRecategorization(String fileId, String annotationId) { + private ManualImageRecategorizationEntity getRecategorization(String fileId, String annotationId) { return recategorizationPersistenceService.findRecategorization(fileId, annotationId); } @Transactional - public void deleteComment(String fileId, List commentIds) { + public void deleteComment(String dossierId, String fileId, List commentIds) { for (var commentId : commentIds) { commentPersistenceService.softDelete(commentId, OffsetDateTime.now()); } // update indicator fileStatusPersistenceService.updateHasComments(fileId, commentPersistenceService.fileHasComments(fileId)); + // if file is currently not analyzing, we can quickly change the redaction log, else we must wait for the analysis to finish. + if (!fileStatusService.getStatus(fileId).getProcessingStatus().equals(ProcessingStatus.PROCESSED)) { + reprocess(dossierId, fileId); + return; + } + fileStatusService.setStatusProcessing(fileId); + RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId); + redactionLog.getRedactionLogEntry().forEach(entry -> entry.getComments().removeIf(comment -> commentIds.contains(comment.getId()))); + fileManagementStorageService.storeRedactionLog(dossierId, fileId, redactionLog); + fileStatusService.setStatusProcessed(fileId); } @@ -573,14 +592,14 @@ public class ManualRedactionService { List response = new ArrayList<>(); - RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId, true, true); + RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId); for (ResizeRedactionRequest resizeRedactionRequest : resizeRedactionRequests) { var resizeRedaction = resizeRedactionPersistenceService.insert(fileId, resizeRedactionRequest); if (resizeRedactionRequest.getComment() != null) { - Long commentId = addComment(fileId, resizeRedactionRequest.getAnnotationId(), resizeRedactionRequest.getComment(), resizeRedactionRequest.getUser()).getId(); + Long commentId = addCommentAndGetId(fileId, resizeRedactionRequest.getAnnotationId(), resizeRedactionRequest.getComment(), resizeRedactionRequest.getUser()); response.add(ManualAddResponse.builder().annotationId(resizeRedactionRequest.getAnnotationId()).commentId(commentId).build()); } @@ -675,7 +694,7 @@ public class ManualRedactionService { @Transactional - public void updateProcessedDate(String fileId, ManualRedactions manualRedactions) { + public void updateProcessedDate(ManualRedactions manualRedactions) { if (manualRedactions != null) { @@ -717,4 +736,13 @@ public class ManualRedactionService { } } + + public Comments getComments(String fileId) { + + return new Comments(commentPersistenceService.findCommentsByFileID(fileId, false) + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> MagicConverter.convert(entry.getValue(), Comment.class)))); + } + } diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionUndoService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionUndoService.java new file mode 100644 index 000000000..6eac5cba6 --- /dev/null +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/manualredactions/ManualRedactionUndoService.java @@ -0,0 +1,247 @@ +package com.iqser.red.service.persistence.management.v1.processor.service.manualredactions; + +import static com.iqser.red.service.persistence.service.v1.api.external.resource.ManualRedactionResource.ANNOTATION_ID; +import static com.iqser.red.service.persistence.service.v1.api.external.resource.ManualRedactionResource.DOSSIER_ID; +import static com.iqser.red.service.persistence.service.v1.api.external.resource.ManualRedactionResource.FILE_ID; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; + +import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; +import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService; +import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService; +import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory; +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.audit.AuditRequest; +import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ManualRedactionWrapperModel; +import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ManualRedactionUndoService { + + private final ManualRedactionService manualRedactionService; + private final AuditPersistenceService auditPersistenceService; + private final FileStatusService fileStatusService; + + + public void undo(String dossierId, String fileId, Set annotationIds) { + + // undo the latest manual redaction for each annotationId + ManualRedactions manualRedactions = manualRedactionService.getManualRedactions(fileId); + + Map manualRedactionWrappers = getLatestManualRedactionsForAnnotationIds(manualRedactions, annotationIds); + + if (manualRedactionWrappers.isEmpty()) { + throw new NotFoundException(String.format("ManualRedaction with annotationIds %s could not be found.", annotationIds)); + } + + undoManualRedactionEntries(dossierId, fileId, manualRedactionWrappers); + undoIdRemovals(dossierId, fileId, manualRedactionWrappers); + undoForceRedactions(dossierId, fileId, manualRedactionWrappers); + undoRecategorization(dossierId, fileId, manualRedactionWrappers); + undoLegalBasisChange(dossierId, fileId, manualRedactionWrappers); + undoResize(dossierId, fileId, manualRedactionWrappers); + reprocess(dossierId, fileId); + } + + private void reprocess(String dossierId, String fileId) { + + fileStatusService.setStatusReprocessForManual(dossierId, fileId, true); + } + + + private void undoResize(String dossierId, String fileId, Map manualRedactionWrappers) { + + List manualResizeRedactions = manualRedactionWrappers.values() + .stream() + .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualResizeRedaction) + .map(ManualRedactionWrapperModel::getId) + .collect(Collectors.toList()); + if (!manualResizeRedactions.isEmpty()) { + manualRedactionService.deleteResizeRedaction(dossierId, fileId, manualResizeRedactions); + manualResizeRedactions.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(fileId) + .category(AuditCategory.DOCUMENT.name()) + .message("Undo of manual resize redaction was done.") + .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) + .build())); + } + } + + + private void undoLegalBasisChange(String dossierId, String fileId, Map manualRedactionWrappers) { + + List manualLegalBasisChanges = manualRedactionWrappers.values() + .stream() + .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualLegalBasisChange) + .map(ManualRedactionWrapperModel::getId) + .collect(Collectors.toList()); + if (!manualLegalBasisChanges.isEmpty()) { + + manualRedactionService.deleteLegalBasisChange(dossierId, fileId, manualLegalBasisChanges); + manualLegalBasisChanges.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(fileId) + .category(AuditCategory.DOCUMENT.name()) + .message("Undo of legal basis change was done.") + .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) + .build())); + } + } + + + private void undoRecategorization(String dossierId, String fileId, Map manualRedactionWrappers) { + + List manualImageRecategorizations = manualRedactionWrappers.values() + .stream() + .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualImageRecategorization) + .map(ManualRedactionWrapperModel::getId) + .collect(Collectors.toList()); + if (!manualImageRecategorizations.isEmpty()) { + + manualRedactionService.deleteRecategorization(dossierId, fileId, manualImageRecategorizations); + manualImageRecategorizations.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(fileId) + .category(AuditCategory.DOCUMENT.name()) + .message("Undo of manual image recategorization was done.") + .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) + .build())); + } + } + + + private void undoForceRedactions(String dossierId, String fileId, Map manualRedactionWrappers) { + + List manualForceRedactions = manualRedactionWrappers.values() + .stream() + .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualForceRedaction) + .map(ManualRedactionWrapperModel::getId) + .collect(Collectors.toList()); + if (!manualForceRedactions.isEmpty()) { + + manualRedactionService.deleteForceRedaction(dossierId, fileId, manualForceRedactions); + manualForceRedactions.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(fileId) + .category(AuditCategory.DOCUMENT.name()) + .message("Undo of manual force redaction was done.") + .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) + .build())); + } + } + + + private void undoIdRemovals(String dossierId, String fileId, Map manualRedactionWrappers) { + + List idRemovals = manualRedactionWrappers.values() + .stream() + .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof IdRemoval) + .map(ManualRedactionWrapperModel::getId) + .collect(Collectors.toList()); + if (!idRemovals.isEmpty()) { + manualRedactionService.deleteRemoveRedaction(dossierId, fileId, idRemovals); + idRemovals.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(fileId) + .category(AuditCategory.DOCUMENT.name()) + .message("Undo of manual remove redaction was done.") + .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) + .build())); + } + } + + + private void undoManualRedactionEntries(String dossierId, String fileId, Map manualRedactionWrappers) { + + List manualRedactionEntries = manualRedactionWrappers.values() + .stream() + .filter(manualRedactionWrapper -> manualRedactionWrapper.getItem() instanceof ManualRedactionEntry) + .map(ManualRedactionWrapperModel::getId) + .collect(Collectors.toList()); + if (!manualRedactionEntries.isEmpty()) { + manualRedactionService.deleteAddRedaction(dossierId, fileId, manualRedactionEntries); + manualRedactionEntries.forEach(annotationId -> auditPersistenceService.audit(AuditRequest.builder() + .userId(KeycloakSecurity.getUserId()) + .objectId(fileId) + .category(AuditCategory.DOCUMENT.name()) + .message("Undo of manual add redaction was done.") + .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId)) + .build())); + } + } + + + private Map getLatestManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set annotationIds) { + + Map result = new HashMap<>(); + annotationIds.forEach(annotationId -> { + var last = getLatestManualRedactionForAnnotationId(manualRedactions, annotationId); + if (last != null) { + result.put(annotationId, last); + } + }); + + return result; + } + + + private ManualRedactionWrapperModel getLatestManualRedactionForAnnotationId(ManualRedactions manualRedactions, String annotationId) { + + final List manualRedactionWrappers = new ArrayList<>(); + + manualRedactions.getEntriesToAdd() + .stream() + .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) + .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); + + manualRedactions.getImageRecategorization() + .stream() + .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) + .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); + + manualRedactions.getIdsToRemove() + .stream() + .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) + .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); + + manualRedactions.getForceRedactions() + .stream() + .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) + .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); + + manualRedactions.getLegalBasisChanges() + .stream() + .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) + .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); + + manualRedactions.getResizeRedactions() + .stream() + .filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId)) + .forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapperModel(item.getAnnotationId(), item.getRequestDate(), item))); + + var sortedManualRedactionWrappers = manualRedactionWrappers.stream() + .sorted(Comparator.comparing(ManualRedactionWrapperModel::getDate, Comparator.nullsLast(Comparator.reverseOrder()))) + .toList(); + + return sortedManualRedactionWrappers.isEmpty() ? null : sortedManualRedactionWrappers.get(0); + } + +} 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/Comments.java b/persistence-service-v1/persistence-service-shared-api-v1/src/main/java/com/iqser/red/service/persistence/service/v1/api/shared/model/annotations/Comments.java new file mode 100644 index 000000000..07bce8c08 --- /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/Comments.java @@ -0,0 +1,19 @@ +package com.iqser.red.service.persistence.service.v1.api.shared.model.annotations; + +import java.util.List; +import java.util.Map; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Comments { + + Map> comments; + +}