RED-7317: add getComment Endpoint

* moved manual redaction undo code
This commit is contained in:
Kilian Schuettler 2023-08-29 15:20:04 +02:00
parent 0b173ec930
commit d1d5331202
5 changed files with 346 additions and 206 deletions

View File

@ -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<ManualRedactionWrapperModel> 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<String> annotationIds) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsApprover(dossierId);
manualRedactionUndoService.undo(dossierId, fileId, annotationIds);
ManualRedactions manualRedactions = manualRedactionService.getManualRedactions(fileId);
Map<String, ManualRedactionWrapperModel> manualRedactionWrappers = getLatestManualRedactionsForAnnotationIds(manualRedactions, annotationIds);
if (manualRedactionWrappers.isEmpty()) {
throw new NotFoundException(String.format("ManualRedaction with annotationIds %s could not be found.", annotationIds));
}
List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String, ManualRedactionWrapperModel> getLatestManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set<String> annotationIds) {
Map<String, ManualRedactionWrapperModel> 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 + "')")

View File

@ -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);
}

View File

@ -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<String> 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<ManualAddResponse> addRecategorization(String dossierId, String fileId, List<RecategorizationRequest> recategorizationRequests) {
var response = new ArrayList<ManualAddResponse>();
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<String> 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<String> annotationIds) {
public void deleteRecategorization(String dossierId, String fileId, List<String> 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<Long> commentIds) {
public void deleteComment(String dossierId, String fileId, List<Long> 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<ManualAddResponse> 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))));
}
}

View File

@ -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<String> annotationIds) {
// undo the latest manual redaction for each annotationId
ManualRedactions manualRedactions = manualRedactionService.getManualRedactions(fileId);
Map<String, ManualRedactionWrapperModel> 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<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> 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<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> 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<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> 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<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> 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<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> 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<String, ManualRedactionWrapperModel> manualRedactionWrappers) {
List<String> 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<String, ManualRedactionWrapperModel> getLatestManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set<String> annotationIds) {
Map<String, ManualRedactionWrapperModel> 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<ManualRedactionWrapperModel> 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);
}
}

View File

@ -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<String, List<Comment>> comments;
}