RED-7317: Keep user-edited paragraph/location when recategorizing entity #101

Merged
kilian.schuettler1 merged 7 commits from RED-7317 into master 2023-08-30 15:53:48 +02:00
48 changed files with 1835 additions and 1823 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,93 +18,53 @@ 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.ManualRedactionService;
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
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;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CommentResponse;
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.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.manual.AddCommentRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ForceRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ImageRecategorizationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ManualRedactionWrapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequest;
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;
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;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ManualRedactionController implements ManualRedactionResource {
private static final String FILE_ID = "fileId";
private static final String DOSSIER_ID = "dossierId";
private static final String ANNOTATION_ID = "annotationId";
private final ManualRedactionService manualRedactionService;
private final DossierManagementService dossierManagementService;
private final AuditPersistenceService auditPersistenceService;
private final AccessControlService accessControlService;
private ManualRedactionWrapper getLatestManualRedactionForAnnotationId(ManualRedactions manualRedactions, String annotationId) {
final List<ManualRedactionWrapper> manualRedactionWrappers = new ArrayList<>();
manualRedactions.getEntriesToAdd()
.stream()
.filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId))
.forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)));
manualRedactions.getImageRecategorization()
.stream()
.filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId))
.forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)));
manualRedactions.getIdsToRemove()
.stream()
.filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId))
.forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)));
manualRedactions.getForceRedactions()
.stream()
.filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId))
.forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)));
manualRedactions.getLegalBasisChanges()
.stream()
.filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId))
.forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)));
manualRedactions.getResizeRedactions()
.stream()
.filter(item -> item.getSoftDeletedTime() == null && item.getAnnotationId().equals(annotationId))
.forEach(item -> manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item)));
var sortedManualRedactionWrappers = manualRedactionWrappers.stream()
.sorted(Comparator.comparing(ManualRedactionWrapper::getDate, Comparator.nullsLast(Comparator.reverseOrder())))
.collect(Collectors.toList());
return sortedManualRedactionWrappers.isEmpty() ? null : sortedManualRedactionWrappers.get(0);
}
final static String FILE_ID = "fileId";
final static String DOSSIER_ID = "dossierId";
final static String ANNOTATION_ID = "annotationId";
ManualRedactionService manualRedactionService;
ManualRedactionUndoService manualRedactionUndoService;
DossierManagementService dossierManagementService;
AuditPersistenceService auditPersistenceService;
AccessControlService accessControlService;
CommentService commentService;
@Override
@ -115,129 +73,8 @@ public class ManualRedactionController implements ManualRedactionResource {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsApprover(dossierId);
manualRedactionUndoService.undo(dossierId, fileId, annotationIds);
ManualRedactions manualRedactions = manualRedactionService.getManualRedactions(fileId);
Map<String, ManualRedactionWrapper> 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(ManualRedactionWrapper::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(ManualRedactionWrapper::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(ManualRedactionWrapper::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(ManualRedactionWrapper::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(ManualRedactionWrapper::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(ManualRedactionWrapper::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, ManualRedactionWrapper> getLatestManualRedactionsForAnnotationIds(ManualRedactions manualRedactions, Set<String> annotationIds) {
Map<String, ManualRedactionWrapper> result = new HashMap<>();
annotationIds.forEach(annotationId -> {
var last = getLatestManualRedactionForAnnotationId(manualRedactions, annotationId);
if (last != null) {
result.put(annotationId, last);
}
});
return result;
}
@ -258,7 +95,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)));
commentService.deleteComment(dossierId, fileId, List.of(Long.valueOf(commentId)));
}
@ -271,17 +108,26 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@Override
@PreAuthorize("hasAuthority('" + READ_MANUAL_REDACTIONS + "')")
public Comments getComments(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
return commentService.getComments(fileId);
}
@Override
@PreAuthorize("hasAuthority('" + ADD_COMMENT + "')")
public CommentResponse addComment(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANNOTATION_ID) String annotationId,
@RequestBody AddCommentRequest addCommentRequest) {
@RequestBody AddCommentRequestModel addCommentRequest) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
var response = manualRedactionService.addComment(fileId,
var response = commentService.addComment(dossierId,
fileId,
annotationId,
CommentRequest.builder().user(KeycloakSecurity.getUserId()).text(addCommentRequest.getText()).build());
@ -301,41 +147,38 @@ public class ManualRedactionController implements ManualRedactionResource {
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public List<ManualAddResponse> addRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<AddRedactionRequest> addRedactionRequests) {
@RequestBody Set<AddRedactionRequestModel> addRedactionRequests) {
var dossier = dossierManagementService.getDossierById(dossierId, false, false);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
if(addRedactionRequests.stream().anyMatch(AddRedactionRequest::isAddToAllDossiers)){
if (addRedactionRequests.stream().anyMatch(AddRedactionRequestModel::isAddToAllDossiers)) {
accessControlService.verifyUserIsApprover(dossierId);
} else {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
}
List<com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AddRedactionRequest> requests = new ArrayList<>();
List<AddRedactionRequest> requests = addRedactionRequests.stream()
.map(addRedactionRequest -> AddRedactionRequest.builder()
.value(addRedactionRequest.getValue())
.legalBasis(addRedactionRequest.getLegalBasis())
.user(KeycloakSecurity.getUserId())
.dossierTemplateTypeId(toTypeId(addRedactionRequest.getType(), dossier.getDossierTemplateId()))
.reason(addRedactionRequest.getReason())
.addToDictionary(addRedactionRequest.isAddToDictionary())
.status(AnnotationStatus.APPROVED)
.comment(addRedactionRequest.getComment() != null ? addRedactionRequest.getComment().getText() : null)
.section(addRedactionRequest.getSection())
.rectangle(addRedactionRequest.isRectangle())
.addToAllDossiers(addRedactionRequest.isAddToAllDossiers())
.forceAddToDictionary(addRedactionRequest.isForceAddToDictionary())
.positions(addRedactionRequest.getPositions())
.sourceId(addRedactionRequest.getSourceId())
.dossierId(dossierId)
.dictionaryEntryType(addRedactionRequest.getDictionaryEntryType())
.build())
.collect(Collectors.toList());
for (var addRedactionRequest : addRedactionRequests) {
var addRedactionRequestBuilder = com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AddRedactionRequest.builder()
.value(addRedactionRequest.getValue())
.legalBasis(addRedactionRequest.getLegalBasis())
.user(KeycloakSecurity.getUserId())
.dossierTemplateTypeId(toTypeId(addRedactionRequest.getType(), dossier.getDossierTemplateId()))
.reason(addRedactionRequest.getReason())
.addToDictionary(addRedactionRequest.isAddToDictionary())
.status(AnnotationStatus.APPROVED)
.comment(addRedactionRequest.getComment() != null ? addRedactionRequest.getComment().getText() : null)
.section(addRedactionRequest.getSection())
.rectangle(addRedactionRequest.isRectangle())
.addToAllDossiers(addRedactionRequest.isAddToAllDossiers())
.forceAddToDictionary(addRedactionRequest.isForceAddToDictionary())
.positions(addRedactionRequest.getPositions())
.sourceId(addRedactionRequest.getSourceId())
.dossierId(dossierId)
.dictionaryEntryType(addRedactionRequest.getDictionaryEntryType());
requests.add(addRedactionRequestBuilder.build());
}
List<ManualAddResponse> responseList = manualRedactionService.addAddRedaction(dossierId, fileId, requests);
responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
@ -351,10 +194,10 @@ public class ManualRedactionController implements ManualRedactionResource {
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public List<ManualAddResponse> removeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RemoveRedactionRequest> removeRedactionRequests) {
@RequestBody Set<RemoveRedactionRequestModel> removeRedactionRequests) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
if(removeRedactionRequests.stream().anyMatch(RemoveRedactionRequest::isRemoveFromAllDossiers)){
if (removeRedactionRequests.stream().anyMatch(RemoveRedactionRequestModel::isRemoveFromAllDossiers)) {
accessControlService.verifyUserIsApprover(dossierId);
} else {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
@ -393,7 +236,7 @@ public class ManualRedactionController implements ManualRedactionResource {
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public List<ManualAddResponse> forceRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ForceRedactionRequest> forceRedactionRequests) {
@RequestBody Set<ForceRedactionRequestModel> forceRedactionRequests) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
@ -424,7 +267,7 @@ public class ManualRedactionController implements ManualRedactionResource {
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public List<ManualAddResponse> legalBasisChangeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<LegalBasisChangeRequest> legalBasisChangeRequests) {
@RequestBody Set<LegalBasisChangeRequestModel> legalBasisChangeRequests) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
@ -456,31 +299,35 @@ public class ManualRedactionController implements ManualRedactionResource {
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public List<ManualAddResponse> recategorizeImageBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ImageRecategorizationRequest> imageRecategorizationRequests) {
public List<ManualAddResponse> recategorizeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RecategorizationRequestModel> recategorizationRequests) {
var dossier = dossierManagementService.getDossierById(dossierId, false, false);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
List<com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ImageRecategorizationRequest> requests = imageRecategorizationRequests.stream()
.map(imageRecategorizationRequest -> com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ImageRecategorizationRequest.builder()
.annotationId(imageRecategorizationRequest.getAnnotationId())
List<RecategorizationRequest> requests = recategorizationRequests.stream()
.map(recategorizationRequest -> RecategorizationRequest.builder()
.annotationId(recategorizationRequest.getAnnotationId())
.user(KeycloakSecurity.getUserId())
.status(AnnotationStatus.APPROVED)
.typeId(toTypeId(imageRecategorizationRequest.getType(), dossier.getDossierTemplateId()))
.comment(imageRecategorizationRequest.getComment())
.dossierTemplateTypeId(toTypeId(recategorizationRequest.getType(), dossier.getDossierTemplateId()))
.comment(recategorizationRequest.getComment())
.dossierId(dossierId)
.addToDictionary(recategorizationRequest.isAddToDictionary())
.addToAllDossiers(recategorizationRequest.isAddToAllDossiers())
.dictionaryEntryType(DictionaryEntryType.ENTRY)
.build())
.collect(Collectors.toList());
List<ManualAddResponse> responseList = manualRedactionService.addImageRecategorization(dossierId, fileId, requests);
List<ManualAddResponse> responseList = manualRedactionService.addRecategorization(dossierId, fileId, requests);
responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Image was recategorized")
.message("Entity was recategorized.")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
.build()));
@ -491,7 +338,8 @@ public class ManualRedactionController implements ManualRedactionResource {
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public List<ManualAddResponse> resizeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ResizeRedactionRequest> resizeRedactionRequests) {
@RequestBody Set<ResizeRedactionRequestModel> resizeRedactionRequests) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);

View File

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

View File

@ -13,15 +13,16 @@ 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.AddCommentRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ForceRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ImageRecategorizationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequest;
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.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;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -58,7 +59,7 @@ public interface ManualRedactionResource {
CommentResponse addComment(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANNOTATION_ID) String annotationId,
@RequestBody AddCommentRequest addCommentRequest);
@RequestBody AddCommentRequestModel addCommentRequest);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@ -77,7 +78,7 @@ public interface ManualRedactionResource {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> addRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<AddRedactionRequest> addRedactionRequest);
@RequestBody Set<AddRedactionRequestModel> addRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@ -86,7 +87,7 @@ public interface ManualRedactionResource {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> removeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RemoveRedactionRequest> removeRedactionRequests);
@RequestBody Set<RemoveRedactionRequestModel> removeRedactionRequests);
@ResponseStatus(value = HttpStatus.OK)
@ -95,7 +96,7 @@ public interface ManualRedactionResource {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> forceRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ForceRedactionRequest> forceRedactionRequests);
@RequestBody Set<ForceRedactionRequestModel> forceRedactionRequests);
@ResponseStatus(value = HttpStatus.OK)
@ -104,16 +105,16 @@ public interface ManualRedactionResource {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> legalBasisChangeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<LegalBasisChangeRequest> legalBasisChangeRequests);
@RequestBody Set<LegalBasisChangeRequestModel> legalBasisChangeRequests);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/bulk/redaction/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Recategorizes the images list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> recategorizeImageBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ImageRecategorizationRequest> imageRecategorizationRequests);
List<ManualAddResponse> recategorizeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RecategorizationRequestModel> recategorizationRequests);
@ResponseStatus(value = HttpStatus.OK)
@ -122,7 +123,7 @@ public interface ManualRedactionResource {
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
List<ManualAddResponse> resizeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ResizeRedactionRequest> resizeRedactionRequests);
@RequestBody Set<ResizeRedactionRequestModel> resizeRedactionRequests);
@ -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

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

View File

@ -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<String> typeIdsOfDictionariesWithAdd = new HashSet<>();
@ElementCollection
@CollectionTable(name = "manual_recategorization_type_ids_of_dictionaries_with_delete")
@Fetch(value = FetchMode.SUBSELECT)
private Set<String> typeIdsOfDictionariesWithDelete = new HashSet<>();
}

View File

@ -9,7 +9,7 @@ import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.IdRemovalEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.migration.Migration;
import com.iqser.red.service.persistence.management.v1.processor.service.ManualRedactionProviderService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionProviderService;
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.RemoveRedactionPersistenceService;

View File

@ -6,7 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.migration.Migration;
import com.iqser.red.service.persistence.management.v1.processor.service.ManualRedactionService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionService;
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;

View File

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

View File

@ -0,0 +1,128 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.CommentEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.CommentPersistenceService;
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.dossiertemplate.dossier.file.ProcessingStatus;
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.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@Service
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class CommentService {
CommentPersistenceService commentPersistenceService;
FileStatusPersistenceService fileStatusPersistenceService;
FileStatusService fileStatusService;
RedactionLogService redactionLogService;
FileManagementStorageService fileManagementStorageService;
@Transactional
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));
// This is an ugly workaround, don't even look at it!
// Basically we should change how comments work and not merge them into the redaction log.
// 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);
}
@Transactional
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))));
}
@Transactional
public CommentEntity addComment(String dossierId, String fileId, String annotationId, CommentRequest commentRequest) {
CommentEntity createdComment = addComment(fileId, annotationId, commentRequest.getText(), commentRequest.getUser());
fileStatusPersistenceService.updateHasComments(fileId, true);
// This is an ugly workaround, don't even look at it!
// Basically we should change how comments work and not merge them into the redaction log.
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;
}
public 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) {
return null;
}
return commentPersistenceService.insert(CommentEntity.builder()
.text(comment)
.fileId(fileId)
.annotationId(annotationId)
.user(user)
.date(OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS))
.build());
}
private void reprocess(String dossierId, String fileId) {
fileStatusService.setStatusReprocessForManual(dossierId, fileId, true);
}
}

View File

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

View File

@ -1,12 +1,11 @@
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;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionService;
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.settings.FileManagementServiceSettings;
@ -16,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;
@ -53,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()) {

View File

@ -3,7 +3,6 @@ package com.iqser.red.service.persistence.management.v1.processor.service;
import static com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingQueueNames.LAYOUT_PARSING_REQUEST_QUEUE;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -13,7 +12,6 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ProcessUntouchedDocumentRequest;
import com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration;
@ -25,6 +23,7 @@ import com.iqser.red.service.persistence.management.v1.processor.model.NerServic
import com.iqser.red.service.persistence.management.v1.processor.model.OCRStatusUpdateResponse;
import com.iqser.red.service.persistence.management.v1.processor.model.image.ImageServiceRequest;
import com.iqser.red.service.persistence.management.v1.processor.service.layoutparsing.LayoutParsingRequestFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionProviderService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;

View File

@ -1,761 +0,0 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.*;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
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.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.*;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.*;
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.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.RedactionLogEntry;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import static com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils.toTypeId;
@Slf4j
@Service
@RequiredArgsConstructor
public class ManualRedactionService {
private final DossierPersistenceService dossierPersistenceService;
private final AddRedactionPersistenceService addRedactionPersistenceService;
private final RemoveRedactionPersistenceService removeRedactionPersistenceService;
private final ForceRedactionPersistenceService forceRedactionPersistenceService;
private final CommentPersistenceService commentPersistenceService;
private final FileStatusPersistenceService fileStatusPersistenceService;
private final DictionaryPersistenceService dictionaryPersistenceService;
private final FileManagementStorageService fileManagementStorageService;
private final ImageRecategorizationPersistenceService recategorizationPersistenceService;
private final LegalBasisChangePersistenceService legalBasisChangePersistenceService;
private final ResizeRedactionPersistenceService resizeRedactionPersistenceService;
private final FileStatusService fileStatusService;
private final ManualRedactionProviderService manualRedactionProviderService;
private final AnalysisFlagsCalculationService analysisFlagsCalculationService;
private final StopwordService stopwordService;
private final RedactionLogService redactionLogService;
private final DictionaryManagementService dictionaryManagementService;
private final HashFunction hashFunction = Hashing.murmur3_128();
@Transactional
public List<ManualAddResponse> addAddRedaction(String dossierId, String fileId, List<AddRedactionRequest> addRedactionRequests) {
var response = new ArrayList<ManualAddResponse>();
dossierPersistenceService.getAndValidateDossier(dossierId);
// validate add to dossier template dictionaries
addRedactionRequests.forEach(request -> dictionaryManagementService.validateAddRemoveToDossierTemplateDictionary(request.getDossierTemplateTypeId(),
request.isAddToDictionary(),
request.isAddToAllDossiers()));
boolean actionPerformed = false;
for (var addRedactionRequest : addRedactionRequests) {
if (addRedactionRequest.isAddToDictionary()) {
validateDictionary(addRedactionRequest);
}
validatePositions(fileId, addRedactionRequest);
String annotationId = hashFunction.hashString(fileId + addRedactionRequest, StandardCharsets.UTF_8).toString();
addRedactionPersistenceService.insert(fileId, annotationId, addRedactionRequest);
Long commentId = null;
if (addRedactionRequest.getComment() != null) {
commentId = addComment(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser()).getId();
}
handleAddToDictionary(fileId,
annotationId,
addRedactionRequest.getDossierTemplateTypeId(),
addRedactionRequest.getValue(),
addRedactionRequest.getStatus(),
addRedactionRequest.isAddToDictionary(),
addRedactionRequest.isAddToAllDossiers(),
false,
dossierId,
addRedactionRequest.getDictionaryEntryType());
response.add(ManualAddResponse.builder().annotationId(annotationId).commentId(commentId).build());
actionPerformed = actionPerformed || addRedactionRequest.getStatus().equals(AnnotationStatus.APPROVED);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
if (actionPerformed) {
reprocess(dossierId, fileId);
}
return response;
}
private void validateDictionary(AddRedactionRequest addRedactionRequest) {
try {
if (!addRedactionRequest.isForceAddToDictionary() && stopwordService.isStopword(addRedactionRequest.getValue())) {
throw new ConflictException("The entry you are trying to add is a stopword");
}
dictionaryPersistenceService.getType(addRedactionRequest.getDossierTemplateTypeId());
} catch (NotFoundException e) {
throw new BadRequestException("Invalid type: " + addRedactionRequest.getDossierTemplateTypeId());
}
}
private void validatePositions(String fileId, AddRedactionRequest addRedactionRequest) {
var numberOfPages = fileStatusService.getStatus(fileId).getNumberOfPages();
addRedactionRequest.getPositions().stream().filter(p -> p.getPage() > numberOfPages).findAny().ifPresent(p -> new BadRequestException("Invalid page found in the request"));
}
private CommentEntity addComment(String fileId, String annotationId, String comment, String user) {
return commentPersistenceService.insert(CommentEntity.builder()
.text(comment)
.fileId(fileId)
.annotationId(annotationId)
.user(user)
.date(OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS))
.build());
}
private boolean handleAddToDictionary(String fileId,
String annotationId,
String dossierTemplateTypeId,
String value,
AnnotationStatus status,
boolean addToDictionary,
boolean addToAllDossiers,
boolean revert,
String dossierId,
DictionaryEntryType dictionaryEntryType) {
if (status == AnnotationStatus.APPROVED) {
if (addToDictionary) {
Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
if (revert) {
var addRedactionToRevert = addRedactionPersistenceService.findAddRedaction(fileId, annotationId);
addRedactionToRevert.getTypeIdsOfModifiedDictionaries().forEach(typeId -> {
removeFromDictionary(typeId, value, dossierId, fileId, dictionaryEntryType);
});
} else {
if (addToAllDossiers) {
var dictionaryEntriesToUnDelete = dictionaryManagementService.getAllEntriesInDossierTemplate(dossierTemplateTypeId, value, dictionaryEntryType);
dictionaryEntriesToUnDelete.forEach(entry -> {
typeIdsOfModifiedDictionaries.add(entry.getTypeId());
addToDictionary(entry.getTypeId(), value, dossierId, fileId, dictionaryEntryType);
});
addToDictionary(dossierTemplateTypeId, value, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(dossierTemplateTypeId);
} else {
addToDictionary(dossierTemplateTypeId + ":" + dossierId, value, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(dossierTemplateTypeId + ":" + dossierId);
}
addRedactionPersistenceService.updateStatus(fileId, annotationId, status, true, typeIdsOfModifiedDictionaries);
}
return true;
}
addRedactionPersistenceService.updateStatus(fileId, annotationId, status, false, null);
}
return false;
}
private void reprocess(String dossierId, String fileId) {
fileStatusService.setStatusReprocessForManual(dossierId, fileId, true);
}
public ManualRedactionEntryEntity getAddRedaction(String fileId, String annotationId) {
return addRedactionPersistenceService.findAddRedaction(fileId, annotationId);
}
private void removeFromDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {
try {
log.debug("Deleting entries to {} for {} / {}", typeId, dossierId, fileId);
dictionaryManagementService.deleteEntries(typeId, List.of(value), dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
} catch (FeignException e) {
throw new BadRequestException(e.getMessage());
}
}
private void addToDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {
try {
log.debug("Adding entry: {} to {} for {} / {}", value, typeId, dossierId, fileId);
dictionaryManagementService.addEntries(typeId, List.of(value), false, false, dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
} catch (Exception e) {
throw new BadRequestException(e.getMessage());
}
}
public List<ManualAddResponse> addRemoveRedaction(String dossierId, String fileId, List<RemoveRedactionRequest> removeRedactionRequests) {
var response = new ArrayList<ManualAddResponse>();
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
RedactionLog redactionLog = fileManagementStorageService.getRedactionLog(dossier.getId(), fileId);
var requiresReAnalysis = false;
var manualRedactions = manualRedactionProviderService.getManualRedactions(fileId);
//validate removing from dossier template dictionary
removeRedactionRequests.forEach(request -> {
if (request.isRemoveFromDictionary()) {
RedactionLogEntry redactionLogEntry = getRedactionLogEntry(redactionLog, request.getAnnotationId());
var dossierTemplateTypeId = toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId());
dictionaryManagementService.validateAddRemoveToDossierTemplateDictionary(dossierTemplateTypeId,
request.isRemoveFromDictionary(),
request.isRemoveFromAllDossiers());
}
});
for (var removeRedactionRequest : removeRedactionRequests) {
if (manualAddRedactionsContains(manualRedactions, removeRedactionRequest.getAnnotationId()) && AnnotationStatus.APPROVED.equals(removeRedactionRequest.getStatus())) {
log.info("hard delete ManualRedactions for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId());
manualRedactionProviderService.hardDeleteManualRedactions(fileId, removeRedactionRequest.getAnnotationId());
} else {
log.info("add removeRedaction for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId());
var idRemoval = MagicConverter.convert(removeRedactionPersistenceService.insert(fileId, removeRedactionRequest), IdRemoval.class);
Long commentId = null;
if (removeRedactionRequest.getComment() != null) {
commentId = addComment(fileId, removeRedactionRequest.getAnnotationId(), removeRedactionRequest.getComment(), removeRedactionRequest.getUser()).getId();
}
boolean matchingEntryFound = false;
if (!removeRedactionRequest.isRemoveFromDictionary() && AnnotationStatus.APPROVED.equals(removeRedactionRequest.getStatus())) {
try {
getRedactionLogEntry(redactionLog, removeRedactionRequest.getAnnotationId());
matchingEntryFound = true;
} catch (NotFoundException e) {
log.warn("No matching entry found in redaction log for annotation id {}", removeRedactionRequest.getAnnotationId());
}
requiresReAnalysis = requiresReAnalysis || matchingEntryFound;
}
var removedFromDictionary = handleRemoveFromDictionary(redactionLog,
dossier,
fileId,
removeRedactionRequest.getAnnotationId(),
removeRedactionRequest.getStatus(),
removeRedactionRequest.isRemoveFromDictionary(),
removeRedactionRequest.isRemoveFromAllDossiers(),
false);
if (!matchingEntryFound && !removedFromDictionary && idRemoval.isApproved()) {
removeRedactionPersistenceService.markAsProcessed(idRemoval);
}
requiresReAnalysis = requiresReAnalysis || removedFromDictionary;
response.add(ManualAddResponse.builder().annotationId(removeRedactionRequest.getAnnotationId()).commentId(commentId).build());
}
}
if (requiresReAnalysis) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
private boolean manualAddRedactionsContains(ManualRedactions manualRedactions, String annotationId) {
return manualRedactions.getEntriesToAdd().stream().anyMatch(m -> annotationId.equals(m.getAnnotationId()));
}
private boolean handleRemoveFromDictionary(RedactionLog redactionLog,
DossierEntity dossier,
String fileId,
String annotationId,
AnnotationStatus status,
boolean removeFromDictionary,
boolean removeFromAllDossiers,
boolean revert) {
if (status == AnnotationStatus.APPROVED) {
Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
if (removeFromDictionary) {
RedactionLogEntry redactionLogEntry = getRedactionLogEntry(redactionLog, annotationId);
if (revert) {
var idRemovalEntity = removeRedactionPersistenceService.findRemoveRedaction(fileId, annotationId);
idRemovalEntity.getTypeIdsOfModifiedDictionaries().forEach(changedTypeId -> {
addToDictionary(changedTypeId, redactionLogEntry.getValue(), dossier.getId(), fileId, DictionaryEntryType.ENTRY);
});
} else {
if (removeFromAllDossiers) {
var dictionaryEntriesToRemove = dictionaryManagementService.getAllEntriesInDossierTemplate(toTypeId(redactionLogEntry.getType(),
dossier.getDossierTemplateId()), redactionLogEntry.getValue(), DictionaryEntryType.ENTRY);
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, annotationId, status, true, typeIdsOfModifiedDictionaries);
return true;
}
removeRedactionPersistenceService.updateStatus(fileId, annotationId, status, false, typeIdsOfModifiedDictionaries);
}
return false;
}
private RedactionLogEntry getRedactionLogEntry(RedactionLog redactionLog, String annotationId) {
Optional<RedactionLogEntry> redactionLogEntryOptional = redactionLog.getRedactionLogEntry().stream().filter(entry -> entry.getId().equals(annotationId)).findFirst();
if (redactionLogEntryOptional.isEmpty()) {
throw new NotFoundException("Annotation does not exist in redaction log.");
}
var redactionLogEntry = redactionLogEntryOptional.get();
return redactionLogEntry;
}
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<ManualAddResponse> addForceRedaction(String dossierId, String fileId, List<ForceRedactionRequest> forceRedactionRequests) {
var response = new ArrayList<ManualAddResponse>();
dossierPersistenceService.getAndValidateDossier(dossierId);
var actionPerformed = false;
for (var forceRedactionRequest : forceRedactionRequests) {
forceRedactionPersistenceService.insert(fileId, forceRedactionRequest);
Long commentId = null;
if (forceRedactionRequest.getComment() != null) {
commentId = addComment(fileId, forceRedactionRequest.getAnnotationId(), forceRedactionRequest.getComment(), forceRedactionRequest.getUser()).getId();
}
actionPerformed = actionPerformed || forceRedactionRequest.getStatus().equals(AnnotationStatus.APPROVED);
response.add(ManualAddResponse.builder().annotationId(forceRedactionRequest.getAnnotationId()).commentId(commentId).build());
}
if (actionPerformed) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
@Transactional
public List<ManualAddResponse> addLegalBasisChange(String dossierId, String fileId, List<LegalBasisChangeRequest> legalBasisChangeRequests) {
var response = new ArrayList<ManualAddResponse>();
dossierPersistenceService.getAndValidateDossier(dossierId);
for (var legalBasisChangeRequest : legalBasisChangeRequests) {
legalBasisChangePersistenceService.insert(fileId, legalBasisChangeRequest);
Long commentId = null;
if (legalBasisChangeRequest.getComment() != null) {
commentId = addComment(fileId, legalBasisChangeRequest.getAnnotationId(), legalBasisChangeRequest.getComment(), legalBasisChangeRequest.getUser()).getId();
}
response.add(ManualAddResponse.builder().annotationId(legalBasisChangeRequest.getAnnotationId()).commentId(commentId).build());
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
@Transactional
public List<ManualAddResponse> addImageRecategorization(String dossierId, String fileId, List<ImageRecategorizationRequest> imageRecategorizationRequests) {
var response = new ArrayList<ManualAddResponse>();
var actionPerformed = false;
dossierPersistenceService.getAndValidateDossier(dossierId);
for (var imageRecategorizationRequest : imageRecategorizationRequests) {
recategorizationPersistenceService.insert(fileId, imageRecategorizationRequest);
Long commentId = null;
if (imageRecategorizationRequest.getComment() != null) {
commentId = addComment(fileId,
imageRecategorizationRequest.getAnnotationId(),
imageRecategorizationRequest.getComment(),
imageRecategorizationRequest.getUser()).getId();
}
actionPerformed = actionPerformed || imageRecategorizationRequest.getStatus().equals(AnnotationStatus.APPROVED);
response.add(ManualAddResponse.builder().annotationId(imageRecategorizationRequest.getAnnotationId()).commentId(commentId).build());
}
if (actionPerformed) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
public CommentEntity addComment(String fileId, String annotationId, CommentRequest commentRequest) {
var createdComment = addComment(fileId, annotationId, commentRequest.getText(), commentRequest.getUser());
fileStatusPersistenceService.updateHasComments(fileId, true);
return createdComment;
}
@Transactional
public void deleteAddRedaction(String dossierId, String fileId, List<String> annotationIds) {
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
var actionPerformed = false;
for (var annotationId : annotationIds) {
var addRedaction = getAddRedaction(fileId, annotationId);
actionPerformed = actionPerformed || handleAddToDictionary(fileId,
annotationId,
addRedaction.getTypeId(),
addRedaction.getValue(),
addRedaction.getStatus(),
addRedaction.isAddToDictionary(),
addRedaction.isAddToAllDossiers(),
true,
dossier.getId(),
addRedaction.getDictionaryEntryType());
addRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
if (actionPerformed) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
@Transactional
public void deleteRemoveRedaction(String dossierId, String fileId, List<String> annotationIds) {
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
var actionPerformed = false;
RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId, true, true);
for (var annotationId : annotationIds) {
var removeRedaction = getRemoveRedaction(fileId, annotationId);
var removedFromDictionary = handleRemoveFromDictionary(redactionLog,
dossier,
fileId,
annotationId,
removeRedaction.getStatus(),
removeRedaction.isRemoveFromDictionary(),
removeRedaction.isRemoveFromAllDossiers(),
true);
actionPerformed = actionPerformed || removedFromDictionary;
removeRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
if (actionPerformed) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
private IdRemovalEntity getRemoveRedaction(String fileId, String annotationId) {
return removeRedactionPersistenceService.findRemoveRedaction(fileId, annotationId);
}
@Transactional
public void deleteForceRedaction(String dossierId, String fileId, List<String> annotationIds) {
var now = OffsetDateTime.now();
for (var annotationId : annotationIds) {
forceRedactionPersistenceService.softDelete(fileId, annotationId, now);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
@Transactional
public void deleteLegalBasisChange(String dossierId, String fileId, List<String> annotationIds) {
for (var annotationId : annotationIds) {
legalBasisChangePersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
@Transactional
public void deleteImageRecategorization(String dossierId, String fileId, List<String> annotationIds) {
var actionPerformed = false;
for (var annotationId : annotationIds) {
var imageRecategorization = getImageRecategorization(fileId, annotationId);
recategorizationPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
actionPerformed = actionPerformed || !AnnotationStatus.REQUESTED.equals(imageRecategorization.getStatus());
}
if (actionPerformed) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
private ManualImageRecategorizationEntity getImageRecategorization(String fileId, String annotationId) {
return recategorizationPersistenceService.findRecategorization(fileId, annotationId);
}
@Transactional
public void deleteComment(String fileId, List<Long> commentIds) {
for (var commentId : commentIds) {
commentPersistenceService.softDelete(commentId, OffsetDateTime.now());
}
// update indicator
fileStatusPersistenceService.updateHasComments(fileId, commentPersistenceService.fileHasComments(fileId));
}
@Transactional
public void deleteResizeRedaction(String dossierId, String fileId, List<String> annotationIds) {
OffsetDateTime now = OffsetDateTime.now();
for (var annotationId : annotationIds) {
resizeRedactionPersistenceService.softDelete(fileId, annotationId, now);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
@Transactional
public List<ManualAddResponse> addResizeRedaction(String dossierId, String fileId, List<ResizeRedactionRequest> resizeRedactionRequests) {
var response = new ArrayList<ManualAddResponse>();
RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId, true, true);
for (ResizeRedactionRequest resizeRedactionRequest : resizeRedactionRequests) {
var resizeRedaction = resizeRedactionPersistenceService.insert(fileId, resizeRedactionRequest);
if (resizeRedactionRequest.getComment() != null) {
var commentId = addComment(fileId, resizeRedactionRequest.getAnnotationId(), resizeRedactionRequest.getComment(), resizeRedactionRequest.getUser()).getId();
response.add(ManualAddResponse.builder().annotationId(resizeRedactionRequest.getAnnotationId()).commentId(commentId).build());
}
updateDictionaryForResizeRedactions(dossierId, fileId, resizeRedaction, redactionLog);
}
if (resizeRedactionRequests.stream().anyMatch(resize -> AnnotationStatus.APPROVED.equals(resize.getStatus()))) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
private void updateDictionaryForResizeRedactions(String dossierId, String fileId, ManualResizeRedactionEntity resizeRedaction, RedactionLog redactionLog) {
RedactionLogEntry redactionLogEntry = getRedactionLogEntry(redactionLog, resizeRedaction.getId().getAnnotationId());
if (resizeRedaction.getUpdateDictionary() != null && resizeRedaction.getUpdateDictionary() && resizeRedaction.getStatus()
.equals(AnnotationStatus.APPROVED) && (redactionLogEntry.isDictionaryEntry() || redactionLogEntry.isDossierDictionaryEntry())) {
var dossier = dossierPersistenceService.findByDossierId(dossierId);
var typeId = buildTypeId(redactionLogEntry, resizeRedaction, dossier);
var newValue = resizeRedaction.getValue();
var oldValue = redactionLogEntry.getValue();
var dictionaryEntryType = getDictionaryEntryType(redactionLogEntry);
boolean isShrinking = oldValue != null && oldValue.length() > newValue.length();
Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
if (isShrinking) {
log.info("Remove old value '{}' from dictionary", oldValue);
removeFromDictionary(typeId, oldValue, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(typeId);
if (resizeRedaction.isAddToAllDossiers() && redactionLogEntry.isDictionaryEntry()) {
String dossierTemplateId = dossier.getDossierTemplateId();
var dossiersOfThisDossierTemplate = dossierPersistenceService.findAllDossiersForDossierTemplateId(dossierTemplateId);
var type = redactionLogEntry.getType();
dossiersOfThisDossierTemplate.forEach(dossierEntity -> {
var typeIdOfDossierEntity = toTypeId(type, dossierTemplateId, dossierEntity.getId());
removeFromDictionary(typeIdOfDossierEntity, oldValue, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(typeIdOfDossierEntity);
});
}
}
log.info("Add new value '{}' to dictionary", newValue);
addToDictionary(typeId, newValue, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(typeId);
resizeRedactionPersistenceService.updateStatus(resizeRedaction.getId().getFileId(),
resizeRedaction.getId().getAnnotationId(),
resizeRedaction.getStatus(),
typeIdsOfModifiedDictionaries);
}
}
private String buildTypeId(RedactionLogEntry redactionLogEntry, ManualResizeRedactionEntity resizeRedaction, DossierEntity dossier) {
if (resizeRedaction.isAddToAllDossiers()) {
return toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId());
} else {
return toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId(), dossier.getId());
}
}
private DictionaryEntryType getDictionaryEntryType(RedactionLogEntry redactionLogEntry) {
if (redactionLogEntry.isRecommendation() && redactionLogEntry.isFalsePositive()) {
return DictionaryEntryType.FALSE_RECOMMENDATION;
} else if (redactionLogEntry.isFalsePositive()) {
return DictionaryEntryType.FALSE_POSITIVE;
} else {
return DictionaryEntryType.ENTRY;
}
}
public ManualRedactions getManualRedactions(String fileId) {
return manualRedactionProviderService.getManualRedactions(fileId);
}
@Transactional
public void updateProcessedDate(String fileId, ManualRedactions manualRedactions) {
if (manualRedactions != null) {
if (manualRedactions.getEntriesToAdd() != null) {
manualRedactions.getEntriesToAdd().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
addRedactionPersistenceService.markAsProcessed(e);
}
});
}
if (manualRedactions.getIdsToRemove() != null) {
manualRedactions.getIdsToRemove().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
removeRedactionPersistenceService.markAsProcessed(e);
}
});
}
if (manualRedactions.getForceRedactions() != null) {
manualRedactions.getForceRedactions().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
forceRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
}
});
}
if (manualRedactions.getImageRecategorization() != null) {
manualRedactions.getImageRecategorization().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
recategorizationPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
}
});
}
if (manualRedactions.getResizeRedactions() != null) {
manualRedactions.getResizeRedactions().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
resizeRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
}
});
}
}
}
}

View File

@ -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<String> excludedTypes, boolean withManualRedactions, boolean includeFalsePositives) {
public RedactionLog getRedactionLog(String dossierId, String fileId, List<String> 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<RedactionLogEntry> it = redactionLogEntries.iterator();

View File

@ -0,0 +1,304 @@
package com.iqser.red.service.persistence.management.v1.processor.service.manualredactions;
import static com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils.toTypeId;
import java.util.Collections;
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.IdRemovalEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualResizeRedactionEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.StopwordService;
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.persistence.FileStatusPersistenceService;
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.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.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ManualRedactionDictionaryUpdateHandler {
DictionaryManagementService dictionaryManagementService;
DictionaryPersistenceService dictionaryPersistenceService;
FileStatusPersistenceService fileStatusPersistenceService;
ResizeRedactionPersistenceService resizeRedactionPersistenceService;
DossierPersistenceService dossierPersistenceService;
StopwordService stopwordService;
public Set<String> handleAddToDictionaryAndReturnModifiedTypeIds(String fileId, String value, ManualRequestWithAddToDictionary manualRequestWithAddToDictionary) {
if (!manualRequestWithAddToDictionary.isAddToDictionary()) {
return Collections.emptySet();
}
Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
if (manualRequestWithAddToDictionary.isAddToAllDossiers()) {
return addToDossierTemplateDictionary(fileId, value, manualRequestWithAddToDictionary, typeIdsOfModifiedDictionaries);
}
return addToDossierDictionary(fileId, value, manualRequestWithAddToDictionary, typeIdsOfModifiedDictionaries);
}
private Set<String> addToDossierTemplateDictionary(String fileId, String value, ManualRequestWithAddToDictionary manualRequestWithAddToDictionary, Set<String> typeIdsOfModifiedDictionaries) {
List<DictionaryEntry> dictionaryEntriesToUnDelete = dictionaryManagementService.getAllEntriesInDossierTemplate(manualRequestWithAddToDictionary.getDossierTemplateTypeId(),
value, manualRequestWithAddToDictionary.getDictionaryEntryType());
dictionaryEntriesToUnDelete.forEach(entry -> {
typeIdsOfModifiedDictionaries.add(entry.getTypeId());
addToDictionary(entry.getTypeId(), value,
manualRequestWithAddToDictionary.getDossierId(), fileId,
manualRequestWithAddToDictionary.getDictionaryEntryType());
});
addToDictionary(manualRequestWithAddToDictionary.getDossierTemplateTypeId(), value,
manualRequestWithAddToDictionary.getDossierId(), fileId,
manualRequestWithAddToDictionary.getDictionaryEntryType());
typeIdsOfModifiedDictionaries.add(manualRequestWithAddToDictionary.getDossierTemplateTypeId());
return typeIdsOfModifiedDictionaries;
}
private Set<String> addToDossierDictionary(String fileId,
String value,
ManualRequestWithAddToDictionary manualRequestWithAddToDictionary,
Set<String> typeIdsOfModifiedDictionaries) {
addToDictionary(manualRequestWithAddToDictionary.getDossierTemplateTypeId() + ":" + manualRequestWithAddToDictionary.getDossierId(),
value,
manualRequestWithAddToDictionary.getDossierId(),
fileId,
manualRequestWithAddToDictionary.getDictionaryEntryType());
typeIdsOfModifiedDictionaries.add(manualRequestWithAddToDictionary.getDossierTemplateTypeId() + ":" + manualRequestWithAddToDictionary.getDossierId());
return typeIdsOfModifiedDictionaries;
}
public Set<String> handleRemoveFromDictionaryAndReturnModifiedTypeIds(RedactionLogEntry redactionLogEntry,
String fileId,
String dossierId,
String dossierTemplateId,
ManualRequestWithRemoveFromDictionary manualRequestWithRemoveFromDictionary) {
if (!manualRequestWithRemoveFromDictionary.isRemoveFromDictionary()) {
return Collections.emptySet();
}
Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
if (manualRequestWithRemoveFromDictionary.isRemoveFromAllDossiers()) {
var dictionaryEntriesToRemove = dictionaryManagementService.getAllEntriesInDossierTemplate(toTypeId(redactionLogEntry.getType(), dossierTemplateId),
redactionLogEntry.getValue(), getDictionaryEntryType(redactionLogEntry));
dictionaryEntriesToRemove.forEach(entry -> {
typeIdsOfModifiedDictionaries.add(entry.getTypeId());
removeFromDictionary(entry.getTypeId(), entry.getValue(), dossierId, fileId, getDictionaryEntryType(redactionLogEntry));
});
} else {
typeIdsOfModifiedDictionaries.add(toTypeId(redactionLogEntry.getType(), dossierTemplateId, dossierId));
removeFromDictionary(toTypeId(redactionLogEntry.getType(), dossierTemplateId, dossierId), redactionLogEntry.getValue(), dossierId, fileId, getDictionaryEntryType(redactionLogEntry));
}
// This is needed to remove resizeRedactions with addToDictionary.
removeResizeRedactionsWithAddToDictionary(dossierTemplateId, redactionLogEntry.getValue());
return typeIdsOfModifiedDictionaries;
}
public Set<String> updateDictionaryForResizeRedactions(String dossierId, String fileId, ManualResizeRedactionEntity resizeRedaction, RedactionLogEntry redactionLogEntry) {
if (resizeRedaction.getUpdateDictionary() != null && resizeRedaction.getUpdateDictionary() && resizeRedaction.getStatus()
.equals(AnnotationStatus.APPROVED) && (redactionLogEntry.isDictionaryEntry() || redactionLogEntry.isDossierDictionaryEntry())) {
var dossier = dossierPersistenceService.findByDossierId(dossierId);
var typeId = buildTypeId(redactionLogEntry, resizeRedaction, dossier);
var newValue = resizeRedaction.getValue();
var oldValue = redactionLogEntry.getValue();
var dictionaryEntryType = getDictionaryEntryType(redactionLogEntry);
boolean isShrinking = oldValue != null && oldValue.length() > newValue.length();
Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
if (isShrinking) {
log.info("Remove old value '{}' from dictionary", oldValue);
removeFromDictionary(typeId, oldValue, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(typeId);
if (resizeRedaction.isAddToAllDossiers() && redactionLogEntry.isDictionaryEntry()) {
String dossierTemplateId = dossier.getDossierTemplateId();
var dossiersOfThisDossierTemplate = dossierPersistenceService.findAllDossiersForDossierTemplateId(dossierTemplateId);
var type = redactionLogEntry.getType();
dossiersOfThisDossierTemplate.forEach(dossierEntity -> {
var typeIdOfDossierEntity = toTypeId(type, dossierTemplateId, dossierEntity.getId());
removeFromDictionary(typeIdOfDossierEntity, oldValue, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(typeIdOfDossierEntity);
});
}
}
log.info("Add new value '{}' to dictionary", newValue);
addToDictionary(typeId, newValue, dossierId, fileId, dictionaryEntryType);
typeIdsOfModifiedDictionaries.add(typeId);
return typeIdsOfModifiedDictionaries;
}
return Collections.emptySet();
}
private String buildTypeId(RedactionLogEntry redactionLogEntry, ManualResizeRedactionEntity resizeRedaction, DossierEntity dossier) {
if (resizeRedaction.isAddToAllDossiers()) {
return toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId());
} else {
return toTypeId(redactionLogEntry.getType(), dossier.getDossierTemplateId(), dossier.getId());
}
}
private DictionaryEntryType getDictionaryEntryType(RedactionLogEntry redactionLogEntry) {
if (redactionLogEntry.isRecommendation() && redactionLogEntry.isFalsePositive()) {
return DictionaryEntryType.FALSE_RECOMMENDATION;
} else if (redactionLogEntry.isFalsePositive()) {
return DictionaryEntryType.FALSE_POSITIVE;
} else {
return DictionaryEntryType.ENTRY;
}
}
public boolean revertAddToDictionary(String fileId, String dossierId, ManualRedactionEntryEntity manualRedactionToRevert) {
if (!manualRedactionToRevert.isAddToDictionary()) {
return false;
}
revertAddToDictionary(manualRedactionToRevert.getValue(),
manualRedactionToRevert.getDictionaryEntryType(),
fileId,
dossierId,
manualRedactionToRevert.getTypeIdsOfModifiedDictionaries());
return true;
}
public void revertAddToDictionary(String value, DictionaryEntryType dictionaryEntryType, String fileId, String dossierId, Set<String> typeIdsOfModifiedDictionaries) {
typeIdsOfModifiedDictionaries.forEach(typeId -> removeFromDictionary(typeId, value, dossierId, fileId, dictionaryEntryType));
}
public boolean revertRemoveFromDictionary(String value, String dossierId, String fileId, IdRemovalEntity idRemoval) {
if (!idRemoval.isRemoveFromDictionary()) {
return false;
}
revertRemoveFromDictionary(value, dossierId, fileId, idRemoval.getTypeIdsOfModifiedDictionaries());
return true;
}
public void revertRemoveFromDictionary(String value, String dossierId, String fileId, Set<String> typeIdsOfModifiedDictionaries) {
typeIdsOfModifiedDictionaries.forEach(changedTypeId -> addToDictionary(changedTypeId, value, dossierId, fileId, DictionaryEntryType.ENTRY));
}
public void validateDictionariesForDelete(ManualRequestWithRemoveFromDictionary request, RedactionLogEntry redactionLogEntry, String dossierTemplateId) {
if (request.isRemoveFromDictionary()) {
var dossierTemplateTypeId = toTypeId(redactionLogEntry.getType(), dossierTemplateId);
dictionaryManagementService.validateAddRemoveToDossierTemplateDictionary(dossierTemplateTypeId, request.isRemoveFromDictionary(), request.isRemoveFromAllDossiers());
}
}
public void validateDictionariesForAdd(ManualRequestWithAddToDictionary addRedactionRequest, String value) {
if (addRedactionRequest.isAddToDictionary()) {
if (addRedactionRequest.isAddToAllDossiers()) {
// validate add to dossier template dictionaries
dictionaryManagementService.validateAddRemoveToDossierTemplateDictionary(addRedactionRequest.getDossierTemplateTypeId());
}
validateDictionary(addRedactionRequest, value);
}
}
private void validateDictionary(ManualRequestWithAddToDictionary addRedactionRequest, String value) {
try {
if (!addRedactionRequest.isForceAddToDictionary() && stopwordService.isStopword(value)) {
throw new ConflictException("The entry you are trying to add is a stopword");
}
dictionaryPersistenceService.getType(addRedactionRequest.getDossierTemplateTypeId());
} catch (NotFoundException e) {
throw new BadRequestException("Invalid type: " + addRedactionRequest.getDossierTemplateTypeId());
}
}
private void addToDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {
try {
log.debug("Adding entry: {} to {} for {} / {}", value, typeId, dossierId, fileId);
dictionaryManagementService.addEntries(typeId, List.of(value), false, false, dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
} catch (Exception e) {
throw new BadRequestException(e.getMessage());
}
}
private void removeFromDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {
try {
log.debug("Deleting entries to {} for {} / {}", typeId, dossierId, fileId);
dictionaryManagementService.deleteEntries(typeId, List.of(value), dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
} catch (FeignException e) {
throw new BadRequestException(e.getMessage());
}
}
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());
}
});
}
}

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
package com.iqser.red.service.persistence.management.v1.processor.service.manualredactions;
import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter.convert;

View File

@ -0,0 +1,407 @@
package com.iqser.red.service.persistence.management.v1.processor.service.manualredactions;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AnalysisFlagsCalculationService;
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.RedactionLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
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.ForceRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ImageRecategorizationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.LegalBasisChangePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RemoveRedactionPersistenceService;
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.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.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;
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.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.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.RedactionLogEntry;
import feign.FeignException;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ManualRedactionService {
DossierPersistenceService dossierPersistenceService;
AddRedactionPersistenceService addRedactionPersistenceService;
RemoveRedactionPersistenceService removeRedactionPersistenceService;
ForceRedactionPersistenceService forceRedactionPersistenceService;
CommentService commentService;
ImageRecategorizationPersistenceService recategorizationPersistenceService;
LegalBasisChangePersistenceService legalBasisChangePersistenceService;
ResizeRedactionPersistenceService resizeRedactionPersistenceService;
FileStatusService fileStatusService;
ManualRedactionProviderService manualRedactionProviderService;
AnalysisFlagsCalculationService analysisFlagsCalculationService;
RedactionLogService redactionLogService;
DictionaryManagementService dictionaryManagementService;
HashFunction hashFunction = Hashing.murmur3_128();
ManualRedactionDictionaryUpdateHandler manualRedactionDictionaryUpdateHandler;
@Transactional
public List<ManualAddResponse> addAddRedaction(String dossierId, String fileId, List<AddRedactionRequest> addRedactionRequests) {
var response = new ArrayList<ManualAddResponse>();
dossierPersistenceService.getAndValidateDossier(dossierId);
for (AddRedactionRequest addRedactionRequest : addRedactionRequests) {
manualRedactionDictionaryUpdateHandler.validateDictionariesForAdd(addRedactionRequest, addRedactionRequest.getValue());
validatePositions(fileId, addRedactionRequest);
String annotationId = hashFunction.hashString(fileId + addRedactionRequest, StandardCharsets.UTF_8).toString();
addRedactionPersistenceService.insert(fileId, annotationId, addRedactionRequest);
Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId, addRedactionRequest.getValue(), addRedactionRequest);
addRedactionPersistenceService.updateStatus(fileId,
annotationId,
addRedactionRequest.getStatus(),
!typeIdsOfModifiedDictionaries.isEmpty(),
typeIdsOfModifiedDictionaries);
Long commentId = commentService.addCommentAndGetId(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser());
response.add(ManualAddResponse.builder().annotationId(annotationId).commentId(commentId).build());
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
if (addRedactionRequests.stream().anyMatch(BaseManualRequest::isApproved)) {
reprocess(dossierId, fileId);
}
return response;
}
public List<ManualAddResponse> addRemoveRedaction(String dossierId, String fileId, List<RemoveRedactionRequest> removeRedactionRequests) {
var response = new ArrayList<ManualAddResponse>();
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
RedactionLog redactionLog = redactionLogService.getRedactionLog(dossier.getId(), fileId);
var requiresReAnalysis = false;
var manualRedactions = manualRedactionProviderService.getManualRedactions(fileId);
//validate removing from dossier template dictionary
for (RemoveRedactionRequest removeRedactionRequest : removeRedactionRequests) {
RedactionLogEntry redactionLogEntry = getRedactionLogEntry(redactionLog, removeRedactionRequest.getAnnotationId());
manualRedactionDictionaryUpdateHandler.validateDictionariesForDelete(removeRedactionRequest, redactionLogEntry, dossier.getDossierTemplateId());
removeRedactionPersistenceService.insert(fileId, removeRedactionRequest);
if (manualAddRedactionsContains(manualRedactions, removeRedactionRequest.getAnnotationId())) {
log.info("hard delete ManualRedactions for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId());
manualRedactionProviderService.hardDeleteManualRedactions(fileId, removeRedactionRequest.getAnnotationId());
requiresReAnalysis = true;
continue;
}
log.info("add removeRedaction for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId());
Long commentId = commentService.addCommentAndGetId(fileId,
removeRedactionRequest.getAnnotationId(),
removeRedactionRequest.getComment(),
removeRedactionRequest.getUser());
Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(//
redactionLogEntry,//
fileId,//
dossierId, //
dossier.getDossierTemplateId(),//
removeRedactionRequest);
boolean removedFromDictionary = !typeIdsOfModifiedDictionaries.isEmpty();
removeRedactionPersistenceService.updateStatus(fileId,
removeRedactionRequest.getAnnotationId(),
removeRedactionRequest.getStatus(),
removedFromDictionary,
typeIdsOfModifiedDictionaries);
requiresReAnalysis = requiresReAnalysis || removedFromDictionary;
response.add(ManualAddResponse.builder().annotationId(removeRedactionRequest.getAnnotationId()).commentId(commentId).build());
}
if (requiresReAnalysis) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
@Transactional
public List<ManualAddResponse> addForceRedaction(String dossierId, String fileId, List<ForceRedactionRequest> forceRedactionRequests) {
var response = new ArrayList<ManualAddResponse>();
dossierPersistenceService.getAndValidateDossier(dossierId);
var requiresReanalysis = false;
for (var forceRedactionRequest : forceRedactionRequests) {
forceRedactionPersistenceService.insert(fileId, forceRedactionRequest);
Long commentId = commentService.addCommentAndGetId(fileId,
forceRedactionRequest.getAnnotationId(),
forceRedactionRequest.getComment(),
forceRedactionRequest.getUser());
requiresReanalysis = requiresReanalysis || forceRedactionRequest.isApproved();
response.add(ManualAddResponse.builder().annotationId(forceRedactionRequest.getAnnotationId()).commentId(commentId).build());
}
if (requiresReanalysis) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
@Transactional
public List<ManualAddResponse> addLegalBasisChange(String dossierId, String fileId, List<LegalBasisChangeRequest> legalBasisChangeRequests) {
var response = new ArrayList<ManualAddResponse>();
dossierPersistenceService.getAndValidateDossier(dossierId);
for (var legalBasisChangeRequest : legalBasisChangeRequests) {
legalBasisChangePersistenceService.insert(fileId, legalBasisChangeRequest);
Long commentId = commentService.addCommentAndGetId(fileId,
legalBasisChangeRequest.getAnnotationId(),
legalBasisChangeRequest.getComment(),
legalBasisChangeRequest.getUser());
response.add(ManualAddResponse.builder().annotationId(legalBasisChangeRequest.getAnnotationId()).commentId(commentId).build());
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
@Transactional
public List<ManualAddResponse> addRecategorization(String dossierId, String fileId, List<RecategorizationRequest> recategorizationRequests) {
var response = new ArrayList<ManualAddResponse>();
var requiresReanalysis = false;
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId);
for (var recategorizationRequest : recategorizationRequests) {
RedactionLogEntry redactionLogEntry = getRedactionLogEntry(redactionLog, recategorizationRequest.getAnnotationId());
manualRedactionDictionaryUpdateHandler.validateDictionariesForAdd(recategorizationRequest, redactionLogEntry.getValue());
manualRedactionDictionaryUpdateHandler.validateDictionariesForDelete(recategorizationRequest, redactionLogEntry, dossier.getDossierTemplateId());
recategorizationPersistenceService.insert(fileId, recategorizationRequest);
Set<String> typeIdsOfDictionariesWithAdd = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId, redactionLogEntry.getValue(), recategorizationRequest);
Set<String> typeIdsOfDictionariesWithDelete = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(redactionLogEntry,
fileId,
recategorizationRequest.getDossierId(),
dossier.getDossierTemplateId(),
recategorizationRequest);
recategorizationPersistenceService.updateStatus(fileId,
recategorizationRequest.getAnnotationId(),
AnnotationStatus.APPROVED,
typeIdsOfDictionariesWithAdd != null,
typeIdsOfDictionariesWithAdd,
typeIdsOfDictionariesWithDelete);
Long commentId = commentService.addCommentAndGetId(fileId,
recategorizationRequest.getAnnotationId(),
recategorizationRequest.getComment(),
recategorizationRequest.getUser());
requiresReanalysis = true;
response.add(ManualAddResponse.builder().annotationId(recategorizationRequest.getAnnotationId()).commentId(commentId).build());
}
if (requiresReanalysis) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
@Transactional
public List<ManualAddResponse> addResizeRedaction(String dossierId, String fileId, List<ResizeRedactionRequest> resizeRedactionRequests) {
List<ManualAddResponse> response = new ArrayList<>();
RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId);
for (ResizeRedactionRequest resizeRedactionRequest : resizeRedactionRequests) {
var resizeRedaction = resizeRedactionPersistenceService.insert(fileId, resizeRedactionRequest);
if (resizeRedactionRequest.getComment() != null) {
Long commentId = commentService.addCommentAndGetId(fileId,
resizeRedactionRequest.getAnnotationId(),
resizeRedactionRequest.getComment(),
resizeRedactionRequest.getUser());
response.add(ManualAddResponse.builder().annotationId(resizeRedactionRequest.getAnnotationId()).commentId(commentId).build());
}
Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.updateDictionaryForResizeRedactions(dossierId,
fileId,
resizeRedaction,
getRedactionLogEntry(redactionLog, resizeRedaction.getId().getAnnotationId()));
resizeRedactionPersistenceService.updateStatus(resizeRedaction.getId().getFileId(),
resizeRedaction.getId().getAnnotationId(),
resizeRedaction.getStatus(),
typeIdsOfModifiedDictionaries);
}
if (resizeRedactionRequests.stream().anyMatch(BaseManualRequest::isApproved)) {
reprocess(dossierId, fileId);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
return response;
}
private void validatePositions(String fileId, AddRedactionRequest addRedactionRequest) {
var numberOfPages = fileStatusService.getStatus(fileId).getNumberOfPages();
addRedactionRequest.getPositions().stream().filter(p -> p.getPage() > numberOfPages).findAny().ifPresent(p -> new BadRequestException("Invalid page found in the request"));
}
private void reprocess(String dossierId, String fileId) {
fileStatusService.setStatusReprocessForManual(dossierId, fileId, true);
}
private void removeFromDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {
try {
log.debug("Deleting entries to {} for {} / {}", typeId, dossierId, fileId);
dictionaryManagementService.deleteEntries(typeId, List.of(value), dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
} catch (FeignException e) {
throw new BadRequestException(e.getMessage());
}
}
private void addToDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {
try {
log.debug("Adding entry: {} to {} for {} / {}", value, typeId, dossierId, fileId);
dictionaryManagementService.addEntries(typeId, List.of(value), false, false, dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
} catch (Exception e) {
throw new BadRequestException(e.getMessage());
}
}
private boolean manualAddRedactionsContains(ManualRedactions manualRedactions, String annotationId) {
return manualRedactions.getEntriesToAdd().stream().anyMatch(m -> annotationId.equals(m.getAnnotationId()));
}
private RedactionLogEntry getRedactionLogEntry(RedactionLog redactionLog, String annotationId) {
return redactionLog.getRedactionLogEntry()
.stream()
.filter(entry -> entry.getId().equals(annotationId))
.findFirst()
.orElseThrow(() -> new NotFoundException("Annotation does not exist in redaction log."));
}
public ManualRedactions getManualRedactions(String fileId) {
return manualRedactionProviderService.getManualRedactions(fileId);
}
@Transactional
public void updateProcessedDate(ManualRedactions manualRedactions) {
if (manualRedactions != null) {
if (manualRedactions.getEntriesToAdd() != null) {
manualRedactions.getEntriesToAdd().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
addRedactionPersistenceService.markAsProcessed(e);
}
});
}
if (manualRedactions.getIdsToRemove() != null) {
manualRedactions.getIdsToRemove().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
removeRedactionPersistenceService.markAsProcessed(e);
}
});
}
if (manualRedactions.getForceRedactions() != null) {
manualRedactions.getForceRedactions().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
forceRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
}
});
}
if (manualRedactions.getImageRecategorization() != null) {
manualRedactions.getImageRecategorization().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
recategorizationPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
}
});
}
if (manualRedactions.getResizeRedactions() != null) {
manualRedactions.getResizeRedactions().forEach(e -> {
if (!e.getStatus().equals(AnnotationStatus.REQUESTED) && e.getProcessedDate() == null) {
resizeRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
}
});
}
}
}
}

View File

@ -0,0 +1,380 @@
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.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
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 org.springframework.transaction.annotation.Transactional;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.IdRemovalEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualImageRecategorizationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AnalysisFlagsCalculationService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.RedactionLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
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.ForceRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ImageRecategorizationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.LegalBasisChangePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RemoveRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ResizeRedactionPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
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.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.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ManualRedactionWrapperModel;
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.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
@Service
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ManualRedactionUndoService {
ManualRedactionProviderService manualRedactionProviderService;
AuditPersistenceService auditPersistenceService;
FileStatusService fileStatusService;
AnalysisFlagsCalculationService analysisFlagsCalculationService;
ImageRecategorizationPersistenceService recategorizationPersistenceService;
DossierPersistenceService dossierPersistenceService;
AddRedactionPersistenceService addRedactionPersistenceService;
RemoveRedactionPersistenceService removeRedactionPersistenceService;
ForceRedactionPersistenceService forceRedactionPersistenceService;
LegalBasisChangePersistenceService legalBasisChangePersistenceService;
ResizeRedactionPersistenceService resizeRedactionPersistenceService;
RedactionLogService redactionLogService;
ManualRedactionDictionaryUpdateHandler manualRedactionDictionaryUpdateHandler;
@Transactional
public void undo(String dossierId, String fileId, Set<String> annotationIds) {
// undo the latest manual redaction for each annotationId
ManualRedactions manualRedactions = 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 ManualRedactions getManualRedactions(String fileId) {
return manualRedactionProviderService.getManualRedactions(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()) {
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 deleteResizeRedaction(String dossierId, String fileId, List<String> annotationIds) {
OffsetDateTime now = OffsetDateTime.now();
for (var annotationId : annotationIds) {
resizeRedactionPersistenceService.softDelete(fileId, annotationId, now);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
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()) {
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 deleteLegalBasisChange(String dossierId, String fileId, List<String> annotationIds) {
for (var annotationId : annotationIds) {
legalBasisChangePersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
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()) {
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 deleteRecategorization(String dossierId, String fileId, List<String> annotationIds) {
dossierPersistenceService.getAndValidateDossier(dossierId);
RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId);
for (var annotationId : annotationIds) {
ManualImageRecategorizationEntity recategorizationEntity = recategorizationPersistenceService.findRecategorization(fileId, annotationId);
String originalValue = getRedactionLogEntry(redactionLog, annotationId).getValue();
manualRedactionDictionaryUpdateHandler.revertRemoveFromDictionary(originalValue, dossierId, fileId, recategorizationEntity.getTypeIdsOfDictionariesWithDelete());
manualRedactionDictionaryUpdateHandler.revertAddToDictionary(originalValue, DictionaryEntryType.ENTRY, fileId, dossierId, recategorizationEntity.getTypeIdsOfDictionariesWithAdd());
recategorizationPersistenceService.updateStatus(fileId, annotationId, AnnotationStatus.APPROVED, false, Collections.emptySet(), Collections.emptySet());
recategorizationPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
} analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
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()) {
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 deleteForceRedaction(String dossierId, String fileId, List<String> annotationIds) {
var now = OffsetDateTime.now();
for (var annotationId : annotationIds) {
forceRedactionPersistenceService.softDelete(fileId, annotationId, now);
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
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()) {
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 deleteRemoveRedaction(String dossierId, String fileId, List<String> annotationIds) {
dossierPersistenceService.getAndValidateDossier(dossierId);
RedactionLog redactionLog = redactionLogService.getRedactionLog(dossierId, fileId);
for (String annotationId : annotationIds) {
IdRemovalEntity removeRedaction = removeRedactionPersistenceService.findRemoveRedaction(fileId, annotationId);
String originalValue = getRedactionLogEntry(redactionLog, annotationId).getValue();
boolean dictionaryChanged = manualRedactionDictionaryUpdateHandler.revertRemoveFromDictionary(originalValue, dossierId, fileId, removeRedaction);
removeRedactionPersistenceService.updateStatus(fileId, annotationId, removeRedaction.getStatus(), dictionaryChanged, Collections.emptySet());
removeRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
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()) {
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 void deleteAddRedaction(String dossierId, String fileId, List<String> annotationIds) {
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
for (String annotationId : annotationIds) {
ManualRedactionEntryEntity addRedaction = addRedactionPersistenceService.findAddRedaction(fileId, annotationId);
boolean dictionaryChanged = manualRedactionDictionaryUpdateHandler.revertAddToDictionary(fileId, dossier.getId(), addRedaction);
addRedactionPersistenceService.updateStatus(fileId, annotationId, AnnotationStatus.APPROVED, dictionaryChanged, null);
addRedactionPersistenceService.softDelete(fileId, annotationId, OffsetDateTime.now());
}
analysisFlagsCalculationService.calculateFlags(dossierId, fileId);
}
private RedactionLogEntry getRedactionLogEntry(RedactionLog redactionLog, String annotationId) {
return redactionLog.getRedactionLogEntry()
.stream()
.filter(entry -> entry.getId().equals(annotationId))
.findFirst()
.orElseThrow(() -> new NotFoundException("Annotation does not exist in redaction log."));
}
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

@ -119,7 +119,7 @@ public class AddRedactionPersistenceService {
boolean isAddOrRemoveFromDictionary,
Set<String> 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);

View File

@ -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;
@ -13,7 +14,7 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.annotati
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.ImageRecategorizationRepository;
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.ImageRecategorizationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RecategorizationRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -26,12 +27,13 @@ public class ImageRecategorizationPersistenceService {
private final ImageRecategorizationRepository imageRecategorizationRepository;
public void insert(String fileId, ImageRecategorizationRequest imageRecategorizationRequest) {
public void insert(String fileId, RecategorizationRequest recategorizationRequest) {
ManualImageRecategorizationEntity manualImageRecategorization = new ManualImageRecategorizationEntity();
manualImageRecategorization.setId(new AnnotationEntityId(imageRecategorizationRequest.getAnnotationId(), fileId));
BeanUtils.copyProperties(imageRecategorizationRequest, manualImageRecategorization);
manualImageRecategorization.setId(new AnnotationEntityId(recategorizationRequest.getAnnotationId(), fileId));
BeanUtils.copyProperties(recategorizationRequest, manualImageRecategorization);
manualImageRecategorization.setRequestDate(OffsetDateTime.now());
manualImageRecategorization.setTypeId(recategorizationRequest.getDossierTemplateTypeId());
imageRecategorizationRepository.saveAndFlush(manualImageRecategorization);
}
@ -43,6 +45,25 @@ public class ImageRecategorizationPersistenceService {
imageRecategorizationRepository.updateStatus(new AnnotationEntityId(annotationId, fileId), annotationStatus);
}
@Transactional
public void updateStatus(String fileId,
String annotationId,
AnnotationStatus annotationStatus,
boolean modifiedDictionary,
Set<String> typeIdsOfDictionaryWithAdd,
Set<String> 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) {

View File

@ -1,550 +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<Integer> excludedPages, List<Type> 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<String> 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<ManualRedactionWrapper> createManualRedactionWrappers(ManualRedactions manualRedactions) {
List<ManualRedactionWrapper> 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<ManualRedactionWrapper> manualRedactionWrappers, RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors) {
manualRedactionWrappers.forEach(mrw -> {
Object item = mrw.getItem();
if (item instanceof ManualImageRecategorization) {
var imageRecategorization = (ManualImageRecategorization) item;
processManualImageRecategorization(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 processManualImageRecategorization(RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors, ManualImageRecategorization imageRecategorization) {
String manualOverrideReason = null;
if (imageRecategorization.getStatus().equals(AnnotationStatus.APPROVED)) {
redactionLogEntry.setType(imageRecategorization.getType());
redactionLogEntry.setSection("Image:" + redactionLogEntry.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<Type> 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<Type> 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<Type> 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<Type> 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<RedactionLogEntry> addManualAddEntries(SectionGrid sectionGrid,
Set<ManualRedactionEntry> manualAdds,
Map<String, List<Comment>> comments,
Colors colors,
List<Type> types,
int analysisNumber) {
List<RedactionLogEntry> 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<RedactionLogComment> convert(List<Comment> 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<Rectangle> convertPositions(List<com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle> 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<Type> types, int analysisNumber) {
var addToDictionary = manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary();
var change = ManualChange.from(manualRedactionEntry).withManualRedactionType(addToDictionary ? ManualRedactionType.ADD_TO_DICTIONARY : ManualRedactionType.ADD_LOCALLY);
List<ManualChange> 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<Type> 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<Type> 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<Type> types, String type) {
var matchingTypes = getMatchingTypes(types, type);
Optional<Type> foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst();
if (foundAndNotDeletedType.isPresent()) {
return ColorUtils.convertColor(foundAndNotDeletedType.get().getHexColor());
}
Optional<Type> firstDeletedType = matchingTypes.stream().findFirst();
return firstDeletedType.map(value -> ColorUtils.convertColor(value.getHexColor())).orElseGet(() -> ColorUtils.convertColor(DELETED_TYPE_COLOR));
}
boolean isHint(List<Type> types, String type) {
var matchingTypes = getMatchingTypes(types, type);
Optional<Type> foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst();
if (foundAndNotDeletedType.isPresent()) {
return foundAndNotDeletedType.get().isHint();
}
Optional<Type> firstDeletedType = matchingTypes.stream().findFirst();
return firstDeletedType.map(Type::isHint).orElse(false);
}
private List<Type> getMatchingTypes(List<Type> 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<ManualRedactionWrapper> {
private String id;
private OffsetDateTime date;
private Object item;
@Override
public int compareTo(ManualRedactionWrapper o) {
return this.date.compareTo(o.date);
}
}
}

View File

@ -8,9 +8,9 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations
public class ManualImageRecategorizationMapper implements BiConsumer<ManualImageRecategorizationEntity, ManualImageRecategorization> {
@Override
public void accept(ManualImageRecategorizationEntity manualRedactionEntryEntity, ManualImageRecategorization manualRedactionEntry) {
public void accept(ManualImageRecategorizationEntity manualImageRecategorizationEntity, ManualImageRecategorization manualRedactionEntry) {
manualRedactionEntry.setType(manualRedactionEntryEntity.getTypeId().substring(0, manualRedactionEntryEntity.getTypeId().indexOf(":")));
manualRedactionEntry.setType(manualImageRecategorizationEntity.getTypeId().substring(0, manualImageRecategorizationEntity.getTypeId().indexOf(":")));
}
}

View File

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

View File

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

View File

@ -2,8 +2,6 @@ package com.iqser.red.service.peristence.v1.server.integration.service;
import static org.assertj.core.api.Assertions.assertThat;
import java.awt.Color;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -11,9 +9,7 @@ import com.iqser.red.service.peristence.v1.server.integration.client.DictionaryC
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
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.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
@Service
public class TypeProvider {
@ -21,6 +17,7 @@ public class TypeProvider {
@Autowired
private DictionaryClient dictionaryClient;
public TypeValue testAndProvideType(DossierTemplateModel dossierTemplate) {
return testAndProvideType(dossierTemplate, null, "test", false);
@ -32,9 +29,13 @@ public class TypeProvider {
return testAndProvideType(dossierTemplate, dossier, typeName, false);
}
public TypeValue testAndProvideType(DossierTemplateModel dossierTemplate, Dossier dossier, String typeName, boolean dossierDictionaryOnly) {
return testAndProvideType(dossierTemplate, dossier, typeName, dossierDictionaryOnly, 100);
}
public TypeValue testAndProvideType(DossierTemplateModel dossierTemplate, Dossier dossier, String typeName, boolean dossierDictionaryOnly, int rank) {
var type = new CreateTypeValue();
@ -53,9 +54,9 @@ public class TypeProvider {
type.setDossierDictionaryOnly(dossierDictionaryOnly);
dictionaryClient.addType(type);
var allTypes = dictionaryClient.getAllTypes(dossierTemplate.getDossierTemplateId(),dossier != null ? dossier.getId() : null,false);
var allTypes = dictionaryClient.getAllTypes(dossierTemplate.getDossierTemplateId(), dossier != null ? dossier.getId() : null, false);
var foundType =allTypes.getTypes().stream().filter(t -> t.getType().equalsIgnoreCase(typeName)).findAny();
var foundType = allTypes.getTypes().stream().filter(t -> t.getType().equalsIgnoreCase(typeName)).findAny();
assertThat(foundType).isPresent();
return foundType.get();

View File

@ -1,27 +1,7 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import com.google.common.collect.Sets;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.iqser.red.service.peristence.v1.server.integration.client.*;
import com.iqser.red.service.peristence.v1.server.integration.service.*;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.*;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileAttributeConfig;
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.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.manual.*;
import feign.FeignException;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
@ -31,7 +11,59 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import com.google.common.collect.Sets;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileAttributeClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileAttributeConfigClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileManagementClient;
import com.iqser.red.service.peristence.v1.server.integration.client.ManualRedactionClient;
import com.iqser.red.service.peristence.v1.server.integration.client.ReanalysisClient;
import com.iqser.red.service.peristence.v1.server.integration.client.RedactionLogClient;
import com.iqser.red.service.peristence.v1.server.integration.client.UploadClient;
import com.iqser.red.service.peristence.v1.server.integration.client.ViewedPagesClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
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.service.TypeProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.UserProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributes;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributesConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PageExclusionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PageRange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ViewedPagesRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileAttributeConfig;
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.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.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.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.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLogEntry;
import feign.FeignException;
import lombok.SneakyThrows;
public class FileTest extends AbstractPersistenceServerServiceTest {
@ -98,7 +130,6 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
assertThat(fileClient.getDossierStatus(dossier.getId()).size()).isEqualTo(1);
var loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
fileManagementClient.deleteFile(dossier.getId(), file.getId());
var nrOfFiles = fileClient.getSoftDeletedDossierStatus(dossier.getId()).size();
@ -325,31 +356,38 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
var type = typeProvider.testAndProvideType(dossierTemplate, null, "manual");
var annotationId = "imagine_this_makes_sense";
RedactionLog redactionLog = new RedactionLog();
RedactionLogEntry redactionLogEntry = RedactionLogEntry.builder().type(type.getType()).id(annotationId).build();
redactionLog.getRedactionLogEntry().add(redactionLogEntry);
when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(redactionLog);
assertThat(fileClient.getDossierStatus(dossier.getId()).size()).isEqualTo(1);
var addRedaction = manualRedactionClient.addRedactionBulk(dossierId,
fileId,
Set.of(AddRedactionRequest.builder()
.addToDictionary(true)
.type(type.getType())
.addToAllDossiers(true)
.reason("1")
.value("test")
.legalBasis("1")
.dictionaryEntryType(DictionaryEntryType.ENTRY)
.build())).iterator().next();
var addRedactionRequest = AddRedactionRequestModel.builder()
.addToDictionary(true)
.type(type.getType())
.addToAllDossiers(true)
.reason("1")
.value("test")
.legalBasis("1")
.dictionaryEntryType(DictionaryEntryType.ENTRY)
.build();
manualRedactionClient.addRedactionBulk(dossierId, fileId, Set.of(addRedactionRequest));
manualRedactionClient.removeRedactionBulk(dossierId,
fileId,
Set.of(RemoveRedactionRequest.builder().annotationId(addRedaction.getAnnotationId()).comment("comment").removeFromDictionary(false).build()));
Set.of(RemoveRedactionRequestModel.builder().annotationId(annotationId).comment("comment").removeFromDictionary(false).build()));
manualRedactionClient.forceRedactionBulk(dossierId,
fileId,
Set.of(ForceRedactionRequest.builder().annotationId("forceRedactionAnnotation").comment("comment").legalBasis("1").build()));
manualRedactionClient.legalBasisChangeBulk(dossierId,
Set.of(ForceRedactionRequestModel.builder().annotationId("forceRedactionAnnotation").comment("comment").legalBasis("1").build()));
manualRedactionClient.legalBasisChangeBulk(dossierId,
fileId,
Set.of(LegalBasisChangeRequest.builder().annotationId("legalBasisChangeAnnotation").comment("comment").legalBasis("1").build()));
manualRedactionClient.recategorizeImageBulk(dossierId,
Set.of(LegalBasisChangeRequestModel.builder().annotationId("legalBasisChangeAnnotation").comment("comment").legalBasis("1").build()));
manualRedactionClient.recategorizeBulk(dossierId,
fileId,
Set.of(ImageRecategorizationRequest.builder().annotationId("imageRecategorizationAnnotation").comment("comment").type("new-type").build()));
Set.of(RecategorizationRequestModel.builder().annotationId(annotationId).comment("comment").type("new-type").build()));
var loadedFile = fileClient.getFileStatus(dossierId, fileId);
@ -396,20 +434,19 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
var addRedaction = manualRedactionClient.addRedactionBulk(dossierId,
fileId,
Set.of(AddRedactionRequest.builder()
Set.of(AddRedactionRequestModel.builder()
.addToDictionary(true)
.addToAllDossiers(true)
.comment(new AddCommentRequest("comment"))
.comment(new AddCommentRequestModel("comment"))
.type(type.getType())
.reason("1")
.value("test")
.legalBasis("1")
.dictionaryEntryType(DictionaryEntryType.ENTRY)
.dictionaryEntryType(DictionaryEntryType.ENTRY)
.build())).iterator().next();
var loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
reanalysisClient.toggleExclusion(dossier.getId(), file.getId(), true);
loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
assertThat(loadedFile.isExcluded()).isTrue();
@ -433,7 +470,6 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
var file = fileTesterAndProvider.testAndProvideFile(dossier, fileName);
String fileId = file.getId();
var type = typeProvider.testAndProvideType(dossierTemplate, null, "manual");
assertThat(fileClient.getDossierStatus(dossier.getId()).size()).isEqualTo(1);
@ -510,6 +546,7 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
}
@Test
public void testFile6034SetUnderReview() {
@ -537,17 +574,15 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
assertThat(actualMessage).contains(expectedMessage);
exception = Assertions.assertThrows(FeignException.BadRequest.class, () -> {
fileClient.setStatusUnderReview(dossier.getId(), file.getId(), user2);
});
expectedMessage = "User must be dossier member";
actualMessage = exception.getMessage();
actualMessage = exception.getMessage();
assertThat(actualMessage).contains(expectedMessage);
fileClient.setStatusUnderReview(dossier.getId(), file.getId(), altUserId);
loadedFile = fileClient.getFileStatus(dossier.getId(), file.getId());
assertThat(loadedFile.getWorkflowStatus()).isEqualTo(WorkflowStatus.UNDER_REVIEW);
@ -556,6 +591,7 @@ public class FileTest extends AbstractPersistenceServerServiceTest {
assertThat(loadedFile.getLastApprover()).isNull();
}
@Test
@SneakyThrows
public void testUploadFileIsInOcrProcessingWithOcrByDefaultFlagTrue() {

View File

@ -13,12 +13,14 @@ import com.iqser.red.service.persistence.management.v1.processor.service.Redacti
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.EntryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.redactionlog.RedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle;
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.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequest;
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.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;
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 feign.FeignException;
@ -105,12 +107,12 @@ 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(),
file.getId(),
Set.of(RemoveRedactionRequest.builder().annotationId("AnnotationId").removeFromDictionary(true).removeFromAllDossiers(true).build())));//.get(0);
Set.of(RemoveRedactionRequestModel.builder().annotationId("AnnotationId").removeFromDictionary(true).removeFromAllDossiers(true).build())));//.get(0);
var dossierTemplateDictionary = internalDictionaryClient.getDictionaryForType(toTypeId(type.getType(), dossierTemplate.getDossierTemplateId()), null);
assertThat(dossierTemplateDictionary.getEntries().size()).isZero();
@ -131,7 +133,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
manualRedactionClient.addRedactionBulk(dossier.getId(),
file.getId(),
Set.of(AddRedactionRequest.builder()
Set.of(AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -166,7 +168,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
manualRedactionClient.addRedactionBulk(dossier.getId(),
file.getId(),
Set.of(AddRedactionRequest.builder()
Set.of(AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -201,7 +203,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
manualRedactionClient.addRedactionBulk(dossier.getId(),
file.getId(),
Set.of(AddRedactionRequest.builder()
Set.of(AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -238,10 +240,10 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("Luke Skywalker"), false, dossier.getDossierId(), DictionaryEntryType.ENTRY);
var dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
Dictionary dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder("Luke Skywalker", "Darth Vader");
var dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getDossierId());
Dictionary dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getDossierId());
assertThat(dossierDictionary.getEntries()).containsExactlyInAnyOrder("Luke Skywalker");
var redactionLog = new RedactionLog(1,
@ -254,11 +256,11 @@ 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(),
Set.of(RemoveRedactionRequest.builder().annotationId("AnnotationId").removeFromDictionary(true).removeFromAllDossiers(true).build())).get(0);
Set.of(RemoveRedactionRequestModel.builder().annotationId("AnnotationId").removeFromDictionary(true).removeFromAllDossiers(true).build()));
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder("Darth Vader");
@ -302,11 +304,11 @@ 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(),
Set.of(RemoveRedactionRequest.builder().annotationId("AnnotationId").removeFromDictionary(true).build())).get(0);
Set.of(RemoveRedactionRequestModel.builder().annotationId("AnnotationId").removeFromDictionary(true).build())).get(0);
var dossierDictionary = internalDictionaryClient.getDictionaryForType(toTypeId(type.getType(), dossierTemplate.getDossierTemplateId(), dossier.getDossierId()), null);
@ -343,11 +345,11 @@ 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(),
Set.of(RemoveRedactionRequest.builder().annotationId("AnnotationId").removeFromDictionary(true).removeFromAllDossiers(true).build())).get(0);
Set.of(RemoveRedactionRequestModel.builder().annotationId("AnnotationId").removeFromDictionary(true).removeFromAllDossiers(true).build())).get(0);
var dossierDictionary = internalDictionaryClient.getDictionaryForType(toTypeId(type.getType(), dossierTemplate.getDossierTemplateId(), dossier.getDossierId()), null);
assertThat(dossierDictionary.getEntries().size()).isEqualTo(1);
@ -375,7 +377,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
var userId = userProvider.getUserId();
var redactionDos = AddRedactionRequest.builder()
var redactionDos = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -389,7 +391,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
.sourceId("SourceId")
.build();
var redactionDosTempDict = AddRedactionRequest.builder()
var redactionDosTempDict = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -427,8 +429,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,11 +446,10 @@ 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 = ResizeRedactionRequest.builder()
var resizeRedactionDosAndAddToAllDos = ResizeRedactionRequestModel.builder()
.annotationId(addRedactions.get(0).getAnnotationId())
.comment("resized dossier redaction")
.value("test redaction in dossier dictionary")
@ -524,7 +524,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
var userId = userProvider.getUserId();
var redactionDos = AddRedactionRequest.builder()
var redactionDos = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -538,7 +538,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
.sourceId("SourceId")
.build();
var redactionDosTempDict = AddRedactionRequest.builder()
var redactionDosTempDict = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -576,8 +576,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,11 +593,10 @@ 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 = ResizeRedactionRequest.builder()
var resizeRedactionDosAndAddToAllDos = ResizeRedactionRequestModel.builder()
.annotationId(addRedactions.get(0).getAnnotationId())
.comment("resized dossier redaction")
.value("test redaction in dossier")
@ -676,7 +674,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
var userId = userProvider.getUserId();
var redactionDos = AddRedactionRequest.builder()
var redactionDos = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -690,7 +688,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
.sourceId("SourceId")
.build();
var redactionDosTempDict = AddRedactionRequest.builder()
var redactionDosTempDict = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -727,8 +725,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,11 +742,10 @@ 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 = ResizeRedactionRequest.builder()
var resizeRedactionDosTemp = ResizeRedactionRequestModel.builder()
.annotationId(addRedactions.get(1).getAnnotationId())
.comment("resized dossier template redaction")
.value("test redaction in dossier template dictionary")
@ -824,7 +820,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
var userId = userProvider.getUserId();
var redactionDos = AddRedactionRequest.builder()
var redactionDos = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -838,7 +834,7 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
.sourceId("SourceId")
.build();
var redactionDosTempDict = AddRedactionRequest.builder()
var redactionDosTempDict = AddRedactionRequestModel.builder()
.positions(List.of(Rectangle.builder().page(1).topLeftY(1).topLeftX(1).height(1).width(1).build()))
.section("section test")
.addToDictionary(true)
@ -875,8 +871,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,11 +888,10 @@ 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 = ResizeRedactionRequest.builder()
var resizeRedactionDosTemp = ResizeRedactionRequestModel.builder()
.annotationId(addRedactions.get(1).getAnnotationId())
.comment("resized dossier template redaction")
.value("test redaction in dossier template")
@ -956,4 +950,73 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
assertThat(mergedDictForTypeDosTempDictInDossier2.getEntries().get(0)).isEqualTo("test redaction in dossier template");
}
@Test
public void testManualRecategorizeAndUndo() {
var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate);
var file = fileTesterAndProvider.testAndProvideFile(dossier);
var type = typeProvider.testAndProvideType(dossierTemplate);
var type2 = typeProvider.testAndProvideType(dossierTemplate, dossier, "test2", false, 66);
var entries = new ArrayList<String>();
var lukeSkywalker = "Luke Skywalker";
var darthVader = "Darth Vader";
entries.add(lukeSkywalker);
entries.add(darthVader);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);
dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of(lukeSkywalker), false, dossier.getDossierId(), DictionaryEntryType.ENTRY);
Dictionary dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder(lukeSkywalker, darthVader);
Dictionary dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getDossierId());
assertThat(dossierDictionary.getEntries()).containsExactlyInAnyOrder(lukeSkywalker);
Dictionary dossierTemplateDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary2.getEntries()).isEmpty();
Dictionary dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getDossierId());
assertThat(dossierDictionary2.getEntries()).isEmpty();
var annotationId = "AnnotationId";
var redactionLog = new RedactionLog(1,
1,
List.of(RedactionLogEntry.builder().id(annotationId).type(type.getType()).value(lukeSkywalker).isDictionaryEntry(true).build()),
null,
0,
0,
0,
0);
fileManagementStorageService.storeJSONObject(dossier.getId(), file.getId(), FileType.REDACTION_LOG, redactionLog);
when(redactionLogService.getRedactionLog(Mockito.any(), Mockito.any())).thenReturn(redactionLog);
manualRedactionClient.recategorizeBulk(dossier.getId(),
file.getId(),
Set.of(RecategorizationRequestModel.builder().type(type2.getType()).annotationId(annotationId).addToDictionary(true).addToAllDossiers(true).build()));
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder(darthVader);
dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getDossierId());
assertThat(dossierDictionary.getEntries()).isEmpty();
dossierTemplateDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary2.getEntries()).containsExactlyInAnyOrder(lukeSkywalker);
dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getDossierId());
assertThat(dossierDictionary2.getEntries()).isEmpty();
manualRedactionClient.undo(dossier.getDossierId(), file.getFileId(), Set.of(annotationId));
dossierTemplateDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary.getEntries()).containsExactlyInAnyOrder(lukeSkywalker, darthVader);
dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getDossierId());
assertThat(dossierDictionary.getEntries()).containsExactlyInAnyOrder(lukeSkywalker);
dossierTemplateDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), null);
assertThat(dossierTemplateDictionary2.getEntries()).isEmpty();
dossierDictionary2 = dictionaryClient.getDictionaryForType(type2.getType(), type.getDossierTemplateId(), dossier.getDossierId());
assertThat(dossierDictionary2.getEntries()).isEmpty();
}
}

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AddRedactionRequest {
public class AddRedactionRequest implements ManualRequestWithAddToDictionary {
private String user;
private String dossierTemplateTypeId;

View File

@ -0,0 +1,13 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.annotations;
public interface BaseManualRequest {
AnnotationStatus getStatus();
default boolean isApproved() {
return getStatus().equals(AnnotationStatus.APPROVED);
}
}

View File

@ -1,4 +1,7 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.manual;
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;
@ -9,10 +12,8 @@ import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ImageRecategorizationRequest {
public class Comments {
private String annotationId;
private String type;
private String comment;
Map<String, List<Comment>> comments;
}

View File

@ -9,7 +9,7 @@ import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ForceRedactionRequest {
public class ForceRedactionRequest implements BaseManualRequest {
private String annotationId;
private String user;

View File

@ -1,21 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.annotations;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ImageRecategorizationRequest {
private String annotationId;
private String user;
private AnnotationStatus status;
private String typeId;
private String comment;
private int page;
}

View File

@ -0,0 +1,24 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.annotations;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
public interface ManualRequestWithAddToDictionary extends BaseManualRequest {
String getDossierId();
boolean isAddToDictionary();
boolean isAddToAllDossiers();
boolean isForceAddToDictionary();
String getDossierTemplateTypeId();
DictionaryEntryType getDictionaryEntryType();
}

View File

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

View File

@ -0,0 +1,51 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.annotations;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class RecategorizationRequest implements ManualRequestWithAddToDictionary, ManualRequestWithRemoveFromDictionary {
String annotationId;
String user;
AnnotationStatus status;
String dossierTemplateTypeId;
String dossierId;
String comment;
int page;
boolean addToDictionary;
boolean addToAllDossiers;
private DictionaryEntryType dictionaryEntryType;
@Override
public boolean isForceAddToDictionary() {
return true;
}
@Override
public boolean isRemoveFromDictionary() {
return addToDictionary;
}
@Override
public boolean isRemoveFromAllDossiers() {
return addToAllDossiers;
}
}

View File

@ -9,7 +9,7 @@ import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RemoveRedactionRequest {
public class RemoveRedactionRequest implements ManualRequestWithRemoveFromDictionary {
private String annotationId;
private String user;

View File

@ -12,7 +12,7 @@ import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ResizeRedactionRequest {
public class ResizeRedactionRequest implements BaseManualRequest{
private String annotationId;
private String user;

View File

@ -16,7 +16,7 @@ import lombok.NonNull;
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AddRedactionRequest {
public class AddRedactionRequestModel {
@NonNull
private String type;
@ -36,7 +36,7 @@ public class AddRedactionRequest {
@Builder.Default
private List<Rectangle> positions = new ArrayList<>();
private AddCommentRequest comment;
private AddCommentRequestModel comment;
private boolean forceAddToDictionary;

View File

@ -7,7 +7,7 @@ import lombok.Data;
@Data
@AllArgsConstructor
public class ManualRedactionWrapper implements Comparable<ManualRedactionWrapper> {
public class ManualRedactionWrapperModel implements Comparable<ManualRedactionWrapperModel> {
private String id;
private OffsetDateTime date;
@ -15,7 +15,7 @@ public class ManualRedactionWrapper implements Comparable<ManualRedactionWrapper
@Override
public int compareTo(ManualRedactionWrapper o) {
public int compareTo(ManualRedactionWrapperModel o) {
// Descending
return o.date.compareTo(this.date);

View File

@ -0,0 +1,23 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.manual;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class RecategorizationRequestModel {
String annotationId;
String type;
String comment;
boolean addToDictionary;
boolean addToAllDossiers;
}