Merge branch 'RED-9947' into 'master'

RED-9947: Bulk-local redactions (replacement for group redactions)

Closes RED-9947

See merge request redactmanager/persistence-service!714
This commit is contained in:
Maverick Studer 2024-09-03 13:49:47 +02:00
commit 11088ddfef
29 changed files with 121950 additions and 101 deletions

View File

@ -9,6 +9,7 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -21,12 +22,16 @@ import com.iqser.red.service.persistence.management.v1.processor.service.AccessC
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
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.manualredactions.PendingEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
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.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationComments;
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;
@ -35,12 +40,16 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddCommentRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionBulkLocalRequestModel;
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.RecategorizationBulkLocalRequestModel;
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.RemoveRedactionBulkLocalRequestModel;
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.mongo.service.EntityLogMongoService;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.AccessLevel;
@ -57,6 +66,7 @@ public class ManualRedactionController implements ManualRedactionResource {
static final String FILE_ID = "fileId";
static final String DOSSIER_ID = "dossierId";
static final String ANNOTATION_ID = "annotationId";
ManualRedactionService manualRedactionService;
ManualRedactionUndoService manualRedactionUndoService;
DossierManagementService dossierManagementService;
@ -64,9 +74,12 @@ public class ManualRedactionController implements ManualRedactionResource {
AccessControlService accessControlService;
CommentService commentService;
FileStatusManagementService fileStatusManagementService;
FileStatusService fileStatusService;
EntityLogMongoService entityLogMongoService;
PendingEntryFactory pendingEntryFactory;
DictionaryPersistenceService dictionaryPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + DELETE_MANUAL_REDACTION + "')")
public void undo(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@ -82,7 +95,6 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@Override
@PreAuthorize("hasAuthority('" + DELETE_COMMENT + "')")
public void undoComment(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@ -105,7 +117,6 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@Override
@PreAuthorize("hasAuthority('" + READ_MANUAL_REDACTIONS + "')")
public ManualRedactions getManualRedactions(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@ -114,12 +125,12 @@ public class ManualRedactionController implements ManualRedactionResource {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return manualRedactionService.getManualRedactions(fileId,
ManualChangesQueryOptions.builder().includeOnlyUnprocessed(unprocessed).includeDictChanges(includeDictChanges).build());
}
@Override
@PreAuthorize("hasAuthority('" + READ_MANUAL_REDACTIONS + "')")
public AnnotationComments getComments(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId) {
@ -132,7 +143,6 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@Override
@PreAuthorize("hasAuthority('" + ADD_COMMENT + "')")
public CommentResponse addComment(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@ -157,21 +167,16 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@Override
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public ManualRedactionResponse addRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<AddRedactionRequestModel> addRedactionRequests) {
var dossier = dossierManagementService.getDossierById(dossierId, false, false);
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
if (addRedactionRequests.stream()
.anyMatch(AddRedactionRequestModel::isAddToAllDossiers)) {
accessControlService.verifyUserIsApprover(dossierId);
} else {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
}
verifyAccessForDossier(dossierId,
fileId,
addRedactionRequests.stream()
.anyMatch(AddRedactionRequestModel::isAddToAllDossiers));
List<ManualAddResponse> responseList = manualRedactionService.addAddRedaction(dossierId, fileId, addRedactionRequests, dossier);
responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
@ -186,6 +191,28 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public ManualRedactionResponse addRedactionBulkLocal(String dossierId, String fileId, AddRedactionBulkLocalRequestModel addRedactionRequest) {
verifyAccess(dossierId, fileId);
// check if type exists before sending the request to redaction service
dictionaryPersistenceService.getType(addRedactionRequest.getType());
fileStatusService.setStatusBulkLocalRedactionsProcessing(dossierId, fileId, addRedactionRequest);
EntityLogEntry entityLogEntry = pendingEntryFactory.buildAddRedactionBulkLocalEntry(addRedactionRequest);
return ManualRedactionResponse.builder()
.manualAddResponses(List.of(ManualAddResponse.builder()
.annotationId(manualRedactionService.getPendingBulkLocalAnnotationId(fileId, addRedactionRequest))
.entityLogEntry(entityLogEntry)
.build()))
.build();
}
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public ManualRedactionResponse removeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@ -193,14 +220,10 @@ public class ManualRedactionController implements ManualRedactionResource {
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
var dossier = dossierManagementService.getDossierById(dossierId, false, false);
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
if (removeRedactionRequests.stream()
.anyMatch(RemoveRedactionRequestModel::isRemoveFromAllDossiers)) {
accessControlService.verifyUserIsApprover(dossierId);
} else {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
}
verifyAccessForDossier(dossierId,
fileId,
removeRedactionRequests.stream()
.anyMatch(RemoveRedactionRequestModel::isRemoveFromAllDossiers));
List<ManualAddResponse> responseList = manualRedactionService.addRemoveRedaction(dossierId,
fileId,
@ -220,14 +243,40 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public ManualRedactionResponse removeRedactionBulkLocal(String dossierId,
String fileId,
RemoveRedactionBulkLocalRequestModel removeRedactionRequest,
boolean removeUnprocessed) {
verifyAccess(dossierId, fileId);
Set<String> entryIds;
if (!removeRedactionRequest.isRectangle()) {
entryIds = entityLogMongoService.findEntryIdsByValueAndEngineManualWithFilters(removeRedactionRequest.getValue(),
removeRedactionRequest.getOriginTypes(),
removeRedactionRequest.getOriginLegalBases(),
removeRedactionRequest.getPageNumbers());
} else {
entryIds = entityLogMongoService.findEntryIdsByMatchingFullPositionAndEngineManualWithFilters(removeRedactionRequest.getPosition().getRectangle(),
removeRedactionRequest.getOriginTypes(),
removeRedactionRequest.getOriginLegalBases(),
removeRedactionRequest.getPageNumbers());
}
Set<RemoveRedactionRequestModel> removeRedactionRequestModels = entryIds.stream()
.map(entryId -> RemoveRedactionRequestModel.builder().annotationId(entryId).build())
.collect(Collectors.toSet());
return removeRedactionBulk(dossierId, fileId, removeRedactionRequestModels, removeUnprocessed);
}
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public ManualRedactionResponse forceRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ForceRedactionRequestModel> forceRedactionRequests) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
verifyAccessAndDossierExistence(dossierId, fileId);
List<ManualAddResponse> responseList = manualRedactionService.addForceRedaction(dossierId, fileId, forceRedactionRequests);
@ -248,9 +297,7 @@ public class ManualRedactionController implements ManualRedactionResource {
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<LegalBasisChangeRequestModel> legalBasisChangeRequests) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
verifyAccessAndDossierExistence(dossierId, fileId);
List<ManualAddResponse> responseList = manualRedactionService.addLegalBasisChange(dossierId, fileId, legalBasisChangeRequests);
@ -261,6 +308,7 @@ public class ManualRedactionController implements ManualRedactionResource {
.message("Legal basis reason was changed")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
.build()));
return ManualRedactionResponse.builder().manualAddResponses(responseList).build();
}
@ -272,9 +320,7 @@ public class ManualRedactionController implements ManualRedactionResource {
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
var dossier = dossierManagementService.getDossierById(dossierId, false, false);
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
verifyAccess(dossierId, fileId);
List<ManualAddResponse> responseList = manualRedactionService.addRecategorization(dossierId, fileId, dossier, recategorizationRequests, includeUnprocessed);
@ -290,15 +336,47 @@ public class ManualRedactionController implements ManualRedactionResource {
}
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public ManualRedactionResponse recategorizeBulkLocal(String dossierId,
String fileId,
RecategorizationBulkLocalRequestModel recategorizationRequest,
boolean includeUnprocessed) {
verifyAccess(dossierId, fileId);
Set<String> entryIds;
if (!recategorizationRequest.isRectangle()) {
entryIds = entityLogMongoService.findEntryIdsByValueAndEngineManualWithFilters(recategorizationRequest.getValue(),
recategorizationRequest.getOriginTypes(),
recategorizationRequest.getOriginLegalBases(),
recategorizationRequest.getPageNumbers());
} else {
entryIds = entityLogMongoService.findEntryIdsByMatchingFullPositionAndEngineManualWithFilters(recategorizationRequest.getPosition().getRectangle(),
recategorizationRequest.getOriginTypes(),
recategorizationRequest.getOriginLegalBases(),
recategorizationRequest.getPageNumbers());
}
Set<RecategorizationRequestModel> recategorizationRequestModels = entryIds.stream()
.map(entryId -> RecategorizationRequestModel.builder()
.annotationId(entryId)
.type(recategorizationRequest.getType())
.legalBasis(recategorizationRequest.getLegalBasis())
.section(recategorizationRequest.getSection())
.value(recategorizationRequest.getValue())
.build())
.collect(Collectors.toSet());
return recategorizeBulk(dossierId, fileId, recategorizationRequestModels, includeUnprocessed);
}
@PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
public ManualRedactionResponse resizeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<ResizeRedactionRequestModel> resizeRedactionRequests,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
verifyAccessAndDossierExistence(dossierId, fileId);
List<ManualAddResponse> responseList = manualRedactionService.addResizeRedaction(dossierId, fileId, resizeRedactionRequests, includeUnprocessed);
@ -309,7 +387,36 @@ public class ManualRedactionController implements ManualRedactionResource {
.message("Skipped annotation was resized to be redacted")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
.build()));
return ManualRedactionResponse.builder().manualAddResponses(responseList).build();
}
private void verifyAccessForDossier(String dossierId, String fileId, boolean allDossiersAffected) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
if (allDossiersAffected) {
accessControlService.verifyUserIsApprover(dossierId);
} else {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
}
}
private void verifyAccess(String dossierId, String fileId) {
accessControlService.checkAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
}
private void verifyAccessAndDossierExistence(String dossierId, String fileId) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsMemberOrApprover(dossierId);
}
}

View File

@ -16,11 +16,14 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.CommentResp
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationComments;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactionResponse;
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.AddRedactionBulkLocalRequestModel;
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.RecategorizationBulkLocalRequestModel;
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.RemoveRedactionBulkLocalRequestModel;
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;
@ -95,13 +98,38 @@ public interface ManualRedactionResource {
@RequestBody Set<AddRedactionRequestModel> addRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk-local/add"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds a bulk of local redactions", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse addRedactionBulkLocal(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody AddRedactionBulkLocalRequestModel addRedactionRequest);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk-local/remove"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Removes a bulk of local redactions", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse removeRedactionBulkLocal(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody RemoveRedactionBulkLocalRequestModel removeRedactionRequest,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/remove"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Removes the redactions list", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse removeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RemoveRedactionRequestModel> removeRedactionRequests,
@ -139,13 +167,26 @@ public interface ManualRedactionResource {
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Recategorizes the list of redaction log entries", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse recategorizeBulk(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody Set<RecategorizationRequestModel> recategorizationRequests,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk-local/recategorize"
+ DOSSIER_ID_PATH_PARAM
+ FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Recategorizes the list of redaction log entries", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Request contains error."), @ApiResponse(responseCode = "404", description = "Dossier or file not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
ManualRedactionResponse recategorizeBulkLocal(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody RecategorizationBulkLocalRequestModel recategorizationRequest,
@RequestParam(value = "includeUnprocessed", required = false, defaultValue = FALSE) boolean includeUnprocessed);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = MANUAL_REDACTION_REST_PATH
+ "/bulk/redaction/resize"

View File

@ -44,7 +44,7 @@ dependencies {
}
implementation("com.knecon.fforesight:llm-service-api:1.13.0")
api("com.knecon.fforesight:jobs-commons:0.10.0")
api("com.knecon.fforesight:tenant-commons:0.29.0")
api("com.knecon.fforesight:tenant-commons:0.30.0")
api("com.knecon.fforesight:database-tenant-commons:0.24.0") {
exclude(group = "com.knecon.fforesight", module = "tenant-commons")
}

View File

@ -36,6 +36,10 @@ public class MessagingConfiguration {
public static final String REDACTION_RESPONSE_QUEUE_PREFIX = "redaction_response";
public static final String REDACTION_RESPONSE_EXCHANGE = "redaction_response_exchange";
public static final String SEARCH_BULK_LOCAL_TERM_RESPONSE_QUEUE_PREFIX = "search_bulk_local_term_response";
public static final String SEARCH_BULK_LOCAL_TERM_RESPONSE_EXCHANGE = "search_bulk_local_term_response_exchange";
public static final String SEARCH_BULK_LOCAL_TERM_DLQ = "search_bulk_local_term_error";
public static final String PDFTRON_RESPONSE_QUEUE_PREFIX = "pdftron_response";
public static final String PDFTRON_RESPONSE_EXCHANGE = "pdftron_response_exchange";
@ -193,6 +197,20 @@ public class MessagingConfiguration {
}
@Bean
public DirectExchange searchTermOccurrencesResponseExchange() {
return new DirectExchange(SEARCH_BULK_LOCAL_TERM_RESPONSE_EXCHANGE);
}
@Bean
public Queue searchTermOccurrencesDLQ() {
return QueueBuilder.durable(SEARCH_BULK_LOCAL_TERM_DLQ).build();
}
@Bean
public DirectExchange reportResponseExchange() {
@ -451,9 +469,8 @@ public class MessagingConfiguration {
return QueueBuilder.durable(ENTITY_DLQ).build();
}
// ---- azure-ner-service ----
@Bean
public DirectExchange azureNerRequestExchange() {

View File

@ -4,6 +4,8 @@ public enum AnalyseStatus {
READ_ONLY_PROCESSING,
PROCESSING,
OCR_PROCESSING,
BULK_LOCAL_REDACTIONS_PROCESSING,
BULK_LOCAL_REDACTIONS_PROCESSED,
LAYOUT_UPDATE,
FINISHED,
ERROR

View File

@ -26,7 +26,7 @@ import com.iqser.red.service.persistence.management.v1.processor.entity.configur
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.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.PendingDictionaryEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.PendingEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Change;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ChangeType;
@ -67,7 +67,7 @@ import lombok.extern.slf4j.Slf4j;
public class EntityLogMergeService {
DictionaryPersistenceService dictionaryPersistenceService;
PendingDictionaryEntryFactory pendingDictionaryEntryFactory;
PendingEntryFactory pendingEntryFactory;
EntityLogMongoService entityLogMongoService;
@ -211,7 +211,7 @@ public class EntityLogMergeService {
return unprocessedManualRedactions.getEntriesToAdd()
.stream()
.filter(manualAdd -> !manualAdd.isLocal())
.map(pendingDictionaryEntryFactory::buildAddToDictionaryEntry);
.map(pendingEntryFactory::buildAddToDictionaryEntry);
}
@ -239,11 +239,11 @@ public class EntityLogMergeService {
if (dictionaryChange instanceof ManualRedactionEntry) {
return null; // pending dictionaries are inserted before the manual changes loop
} else if (dictionaryChange instanceof IdRemoval idRemoval) {
return pendingDictionaryEntryFactory.buildRemoveFromDictionary(idRemoval, entityLogEntry);
return pendingEntryFactory.buildRemoveFromDictionary(idRemoval, entityLogEntry);
} else if (dictionaryChange instanceof ManualResizeRedaction manualResizeRedaction) {
return pendingDictionaryEntryFactory.buildResizeWithDictionary(manualResizeRedaction, entityLogEntry);
return pendingEntryFactory.buildResizeWithDictionary(manualResizeRedaction, entityLogEntry);
} else if (dictionaryChange instanceof ManualRecategorization manualRecategorization) {
return pendingDictionaryEntryFactory.buildRecategorizeWithDictionary(manualRecategorization, entityLogEntry);
return pendingEntryFactory.buildRecategorizeWithDictionary(manualRecategorization, entityLogEntry);
} else {
throw new IllegalArgumentException(String.format("Manual change of type %s has no defined dictionary action!", dictionaryChange.getClass()));
}
@ -484,7 +484,7 @@ public class EntityLogMergeService {
&& (entityLogEntry.getEntryType().equals(EntryType.IMAGE) || entityLogEntry.getEntryType().equals(EntryType.IMAGE_HINT))) {
addChanges(entityLogEntry, changes);
return pendingDictionaryEntryFactory.buildPendingImageRecategorizationEntry(recategorization, entityLogEntry);
return pendingEntryFactory.buildPendingImageRecategorizationEntry(recategorization, entityLogEntry);
}
entityLogEntry.getEngines().add(Engine.MANUAL);

View File

@ -52,6 +52,7 @@ import com.iqser.red.service.persistence.management.v1.processor.utils.FileModel
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeResult;
import com.iqser.red.service.persistence.service.v1.api.shared.model.BulkLocalRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
import com.iqser.red.service.persistence.service.v1.api.shared.model.MessageType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.NerServiceRequest;
@ -60,6 +61,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.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.manual.AddRedactionBulkLocalRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.ComponentLogMongoService;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
@ -216,6 +218,31 @@ public class FileStatusService {
}
protected void addSearchTermOccurrencesAnalysisRequestToAnalysisQueue(FileEntity fileStatus, BulkLocalRequest bulkLocalRequest) {
var dossierId = fileStatus.getDossierId();
var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
var fileId = fileStatus.getId();
int analysisVersion = fileStatus.getAnalysisVersion();
MessageType searchBulkLocalTerm = MessageType.SEARCH_BULK_LOCAL_TERM;
log.info("Add file: {} from dossier {} to Analysis queue with MessageType {}", fileId, dossierId, searchBulkLocalTerm);
var analyseRequest = AnalyzeRequest.builder()
.analysisNumber(analysisVersion)
.messageType(searchBulkLocalTerm)
.dossierId(dossierId)
.fileId(fileId)
.dossierTemplateId(dossier.getDossierTemplateId())
.bulkLocalRequest(bulkLocalRequest)
.build();
rabbitTemplate.convertAndSend(MessagingConfiguration.REDACTION_PRIORITY_REQUEST_EXCHANGE, TenantContext.getTenantId(), analyseRequest);
websocketService.sendAnalysisEvent(dossierId, fileId, AnalyseStatus.BULK_LOCAL_REDACTIONS_PROCESSING, analysisVersion);
}
@Transactional
protected void addToAnalysisQueue(String dossierId, String fileId, boolean priority, Set<Integer> sectionsToReanalyse, boolean manualRedactionReanalyse) {
@ -983,4 +1010,27 @@ public class FileStatusService {
}
}
@Transactional
public void setStatusBulkLocalRedactionsProcessing(String dossierId, String fileId, AddRedactionBulkLocalRequestModel addRedactionBulkLocalRequestModel) {
FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
if (fileStatus.isExcluded()) {
log.debug("File {} is excluded", fileStatus.getId());
return;
}
fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.BULK_LOCAL_REDACTIONS_PROCESSING);
BulkLocalRequest bulkLocalRequest = BulkLocalRequest.builder()
.searchTerm(addRedactionBulkLocalRequestModel.getValue())
.type(addRedactionBulkLocalRequestModel.getType())
.reason(addRedactionBulkLocalRequestModel.getReason())
.legalBasis(addRedactionBulkLocalRequestModel.getLegalBasis())
.section(addRedactionBulkLocalRequestModel.getSection())
.pageNumbers(addRedactionBulkLocalRequestModel.getPageNumbers())
.build();
addSearchTermOccurrencesAnalysisRequestToAnalysisQueue(fileStatus, bulkLocalRequest);
}
}

View File

@ -71,6 +71,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
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.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionBulkLocalRequestModel;
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;
@ -111,7 +112,7 @@ public class ManualRedactionService {
ManualRedactionMapper manualRedactionMapper;
RabbitTemplate rabbitTemplate;
EntityLogMergeService entityLogMergeService;
PendingDictionaryEntryFactory pendingDictionaryEntryFactory;
PendingEntryFactory pendingEntryFactory;
@Transactional
@ -135,7 +136,7 @@ public class ManualRedactionService {
manualRedactionDictionaryUpdateHandler.validateDictionariesForAdd(addRedactionRequest, addRedactionRequest.getValue());
validatePositions(fileId, addRedactionRequest);
String annotationId = hashFunction.hashString(fileId + addRedactionRequest, StandardCharsets.UTF_8).toString();
String annotationId = hashString(fileId + addRedactionRequest);
ManualRedactionEntryEntity manualRedactionEntryEntity = addRedactionPersistenceService.insert(fileId, annotationId, addRedactionRequest);
manualRedactionEntryEntities.add(manualRedactionEntryEntity);
@ -154,9 +155,9 @@ public class ManualRedactionService {
getAnalysisNumber(dossierId, fileId),
dossierEntity);
} else {
entityLogEntry = pendingDictionaryEntryFactory.buildAddToDictionaryEntry(MagicConverter.convert(manualRedactionEntryEntity,
ManualRedactionEntry.class,
new ManualRedactionEntryMapper()));
entityLogEntry = pendingEntryFactory.buildAddToDictionaryEntry(MagicConverter.convert(manualRedactionEntryEntity,
ManualRedactionEntry.class,
new ManualRedactionEntryMapper()));
}
Long commentId = commentService.addCommentAndGetId(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser());
response.add(ManualAddResponse.builder().annotationId(annotationId).commentId(commentId).entityLogEntry(entityLogEntry).build());
@ -254,7 +255,7 @@ public class ManualRedactionService {
if (!idRemoval.isRemoveFromAllDossiers() && !idRemoval.isRemoveFromDictionary()) {
entityLogMergeService.mergeIdToRemove(MagicConverter.convert(idRemoval, IdRemoval.class), entityLogEntry, getAnalysisNumber(dossierId, fileId));
} else {
entityLogEntry = pendingDictionaryEntryFactory.buildRemoveFromDictionary(MagicConverter.convert(idRemoval, IdRemoval.class), entityLogEntry);
entityLogEntry = pendingEntryFactory.buildRemoveFromDictionary(MagicConverter.convert(idRemoval, IdRemoval.class), entityLogEntry);
}
response.add(ManualAddResponse.builder().annotationId(removeRedactionRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());
}
@ -413,9 +414,9 @@ public class ManualRedactionService {
entityLogEntry = entry;
}
} else {
entityLogEntry = pendingDictionaryEntryFactory.buildRecategorizeWithDictionary(MagicConverter.convert(recategorizationEntity,
ManualRecategorization.class,
new ManualRecategorizationMapper()), entityLogEntry);
entityLogEntry = pendingEntryFactory.buildRecategorizeWithDictionary(MagicConverter.convert(recategorizationEntity,
ManualRecategorization.class,
new ManualRecategorizationMapper()), entityLogEntry);
}
response.add(ManualAddResponse.builder().annotationId(recategorizationRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());
@ -474,9 +475,9 @@ public class ManualRedactionService {
entityLogEntry,
getAnalysisNumber(dossierId, fileId));
} else {
entityLogEntry = pendingDictionaryEntryFactory.buildResizeWithDictionary(MagicConverter.convert(resizeRedaction,
ManualResizeRedaction.class,
new ManualResizeRedactionMapper()), entityLogEntry);
entityLogEntry = pendingEntryFactory.buildResizeWithDictionary(MagicConverter.convert(resizeRedaction,
ManualResizeRedaction.class,
new ManualResizeRedactionMapper()), entityLogEntry);
}
response.add(ManualAddResponse.builder().annotationId(resizeRedactionRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());
}
@ -699,4 +700,16 @@ public class ManualRedactionService {
return analysisNumber.orElseThrow(() -> new BadRequestException("Can't load latest analysis number"));
}
public String getPendingBulkLocalAnnotationId(String fileId, AddRedactionBulkLocalRequestModel addRedactionBulkLocalRequestModel) {
return hashString(fileId + addRedactionBulkLocalRequestModel);
}
private String hashString(String input) {
return hashFunction.hashString(input, StandardCharsets.UTF_8).toString();
}
}

View File

@ -1,5 +1,7 @@
package com.iqser.red.service.persistence.management.v1.processor.service.manualredactions;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -9,11 +11,14 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Engine;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryState;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ManualChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ManualRedactionType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
@ -24,9 +29,11 @@ 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.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.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionBulkLocalRequestModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
@Service
public class PendingDictionaryEntryFactory {
public class PendingEntryFactory {
private static String shortenValueIfNecessary(String value) {
@ -96,6 +103,42 @@ public class PendingDictionaryEntryFactory {
}
public EntityLogEntry buildAddRedactionBulkLocalEntry(AddRedactionBulkLocalRequestModel addRedactionBulkLocalRequestModel) {
var manualChanges = List.of(ManualChange.builder()
.manualRedactionType(ManualRedactionType.ADD)
.requestedDate(OffsetDateTime.now())
.processedDate(null)
.userId(KeycloakSecurity.getUserId())
.propertyChanges(Map.of("value", addRedactionBulkLocalRequestModel.getValue()))
.build());
return EntityLogEntry.builder()
.id("")
.value(addRedactionBulkLocalRequestModel.getValue())
.type(addRedactionBulkLocalRequestModel.getType())
.entryType(EntryType.ENTITY)
.state(EntryState.PENDING)
.reason(addRedactionBulkLocalRequestModel.getReason())
.legalBasis(addRedactionBulkLocalRequestModel.getLegalBasis())
.matchedRule("")
.containingNodeId(Collections.emptyList())
.closestHeadline("")
.section(addRedactionBulkLocalRequestModel.getSection())
.positions(convertPositions(addRedactionBulkLocalRequestModel.getPositions()))
.textAfter("")
.textBefore("")
.startOffset(-1)
.endOffset(-1)
.changes(Collections.emptyList())
.manualChanges(manualChanges)
.engines(Set.of(Engine.MANUAL))
.reference(Collections.emptySet())
.importedRedactionIntersections(Collections.emptySet())
.build();
}
private List<Position> convertPositions(List<Rectangle> rectangles) {
return rectangles.stream()

View File

@ -0,0 +1,123 @@
package com.iqser.red.service.persistence.management.v1.processor.service.queue;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.model.websocket.AnalyseStatus;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.websocket.WebsocketService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.BulkLocalResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
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.Rectangle;
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.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionRequestModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class SearchTermOccurrencesResponseReceiver {
public static final String SEARCH_BULK_LOCAL_TERM_RESPONSE_LISTENER = "search-bulk-local-term-response-listener";
ObjectMapper objectMapper;
DossierManagementService dossierManagementService;
ManualRedactionService manualRedactionService;
AuditPersistenceService auditPersistenceService;
FileStatusPersistenceService fileStatusPersistenceService;
WebsocketService webSocketService;
@RabbitHandler
@RabbitListener(id = SEARCH_BULK_LOCAL_TERM_RESPONSE_LISTENER)
public void receive(Message message) throws IOException {
BulkLocalResponse response = objectMapper.readValue(message.getBody(), new TypeReference<>() {
});
if (message.getMessageProperties().isRedelivered()) {
throw new AmqpRejectAndDontRequeueException(String.format("Error during last processing of bulk local term search for file %s in dossier %s, do not retry.",
response.getFileId(),
response.getDossierId()));
}
receive(response);
}
public void receive(BulkLocalResponse response) {
var dossier = dossierManagementService.getDossierById(response.getDossierId(), false, false);
Set<AddRedactionRequestModel> addRedactionRequests = response.getPositions()
.stream()
.map(positions -> AddRedactionRequestModel.builder()
.type(response.getType())
.value(response.getSearchTerm())
.reason(response.getReason())
.legalBasis(response.getLegalBasis())
.positions(convertPositions(positions))
.section(response.getSection())
.build())
.collect(Collectors.toSet());
log.info("Received manual redaction requests for file {} in dossier {} after term search", response.getFileId(), dossier.getId());
List<ManualAddResponse> manualAddResponses = manualRedactionService.addAddRedaction(response.getDossierId(), response.getFileId(), addRedactionRequests, dossier);
manualAddResponses.forEach(manualAddResponse -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getFileId())
.category(AuditCategory.DOCUMENT.name())
.message("Manual annotation was added.")
.details(Map.of("dossierId",
response.getDossierId(),
"fileId",
response.getFileId(),
"annotationId",
manualAddResponse.getAnnotationId()))
.build()));
fileStatusPersistenceService.updateProcessingStatus(response.getFileId(), ProcessingStatus.PROCESSED);
int analysisVersion = fileStatusPersistenceService.getStatus(response.getFileId()).getAnalysisVersion();
webSocketService.sendAnalysisEvent(response.getDossierId(),
response.getFileId(),
AnalyseStatus.BULK_LOCAL_REDACTIONS_PROCESSED, analysisVersion);
log.info("Finished adding all manual redactions, the file is processed again");
}
private List<Rectangle> convertPositions(List<Position> positions) {
return positions.stream()
.map(position -> Rectangle.builder().page(position.getPageNumber()).height(position.h()).width(position.w()).topLeftX(position.x()).topLeftY(position.y()).build())
.toList();
}
}

View File

@ -17,6 +17,7 @@ import static com.iqser.red.service.persistence.management.v1.processor.service.
import static com.iqser.red.service.persistence.management.v1.processor.service.queue.OCRProcessingMessageReceiver.OCR_RESPONSE_LISTENER_ID;
import static com.iqser.red.service.persistence.management.v1.processor.service.queue.OCRProcessingMessageReceiver.OCR_STATUS_UPDATE_LISTENER_ID;
import static com.iqser.red.service.persistence.management.v1.processor.service.queue.RedactionAnalysisResponseReceiver.REDACTION_RESPONSE_LISTENER_ID;
import static com.iqser.red.service.persistence.management.v1.processor.service.queue.SearchTermOccurrencesResponseReceiver.SEARCH_BULK_LOCAL_TERM_RESPONSE_LISTENER;
import static com.iqser.red.service.persistence.management.v1.processor.service.queue.VisualLayoutParsingMessageReceiver.VISUAL_LAYOUT_PARSING_RESPONSE_LISTENER_ID;
import java.util.Map;
@ -78,6 +79,13 @@ public class TenantExchangeMessageReceiverImpl extends TenantExchangeMessageRece
.dlqName(REDACTION_DLQ)
.arguments(Map.of("x-max-priority", 2))
.build(),
TenantQueueConfiguration.builder()
.listenerId(SEARCH_BULK_LOCAL_TERM_RESPONSE_LISTENER)
.exchangeName(SEARCH_BULK_LOCAL_TERM_RESPONSE_EXCHANGE)
.queuePrefix(SEARCH_BULK_LOCAL_TERM_RESPONSE_QUEUE_PREFIX)
.dlqName(SEARCH_BULK_LOCAL_TERM_DLQ)
.arguments(Map.of("x-max-priority", 2))
.build(),
TenantQueueConfiguration.builder()
.listenerId(PDFTRON_RESPONSE_LISTENER_ID)
.exchangeName(PDFTRON_RESPONSE_EXCHANGE)

View File

@ -34,7 +34,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.EntityL
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionProviderService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.PendingDictionaryEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.PendingEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
@ -103,7 +103,7 @@ public class EntityLogMergeTest {
@BeforeEach
public void setUp() {
entityLogMergeService = new EntityLogMergeService(dictionaryPersistenceService, new PendingDictionaryEntryFactory(), entityLogMongoService);
entityLogMergeService = new EntityLogMergeService(dictionaryPersistenceService, new PendingEntryFactory(), entityLogMongoService);
}

View File

@ -3,8 +3,11 @@ package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -29,6 +32,7 @@ public class EntityLogMongoServiceTest extends AbstractPersistenceServerServiceT
private final String ENTITY_LOG2 = "files/entity-log/6f2a9d4b5d4e11211fc0d7732fd45b78.ENTITY_LOG.json";
private final String ENTITY_LOG3_BEFORE = "files/entity-log/c3b23116f2d277170d303bfc6fb82e6a-before.ENTITY_LOG.json";
private final String ENTITY_LOG3_AFTER = "files/entity-log/c3b23116f2d277170d303bfc6fb82e6a-after.ENTITY_LOG.json";
private final String ENTITY_LOG_LOCAL_BULK = "files/entity-log/local-bulk-test.ENTITY_LOG.json";
private final String ENTITY_LOG_WITH_CHANGES = "files/entity-log/entitylog-with-changes.json";
private static final String TEST_DOSSIER_ID = "91ce8e90-9aec-473c-b8c3-cbe16443ad34";
@ -326,4 +330,70 @@ public class EntityLogMongoServiceTest extends AbstractPersistenceServerServiceT
assertEquals(response.getEntityLogEntry().size(), 14);
}
@Test
@SneakyThrows
public void testEntityLogMongoServiceMethodsForLocalBulk() {
var file = new ClassPathResource(ENTITY_LOG_LOCAL_BULK);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
EntityLog entityLog = objectMapper.readValue(file.getInputStream(), EntityLog.class);
entityLogMongoService.saveEntityLog(TEST_DOSSIER_ID, TEST_FILE1_ID, entityLog);
String akessonValue = "Akesson";
Set<String> akessonSet = entityLogMongoService.findEntryIdsOfManualsWithValue(akessonValue);
assertEquals(akessonSet.size(), 3);
float[] akessonRectangle = {322.63608f, 308.1f, 41.375977f, 12.642f};
Set<String> akessonSetByPositions = entityLogMongoService.findEntryIdsOfManualsWithPositionRectangle(akessonRectangle);
assertEquals(akessonSetByPositions.size(), 3);
Set<String> akessonSetFilterQuery = entityLogMongoService.findEntryIdsByMatchingFullPositionAndEngineManualWithFilters(akessonRectangle,
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet());
assertEquals(akessonSetFilterQuery.size(), 3);
Set<String> akessonSetFilterQueryByPositions = entityLogMongoService.findEntryIdsByValueAndEngineManualWithFilters(akessonValue,
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet());
assertEquals(akessonSetFilterQueryByPositions.size(), 3);
akessonSetFilterQuery = entityLogMongoService.findEntryIdsByMatchingFullPositionAndEngineManualWithFilters(akessonRectangle,
Collections.emptySet(),
Collections.emptySet(),
Set.of(1, 3));
assertEquals(akessonSetFilterQuery.size(), 2);
akessonSetFilterQueryByPositions = entityLogMongoService.findEntryIdsByValueAndEngineManualWithFilters(akessonValue,
Collections.emptySet(),
Collections.emptySet(),
Set.of(1, 3));
assertEquals(akessonSetFilterQueryByPositions.size(), 2);
String baldridgeValue = "Baldridge";
Set<String> baldridgeSet = entityLogMongoService.findEntryIdsByValueAndEngineManualWithFilters(baldridgeValue,
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet());
assertEquals(baldridgeSet.size(), 4);
Set<String> baldridgeSetWithTypeFilter = entityLogMongoService.findEntryIdsByValueAndEngineManualWithFilters(baldridgeValue,
Set.of("CBI_author", "Test"),
Collections.emptySet(),
Collections.emptySet());
assertEquals(baldridgeSetWithTypeFilter.size(), 2);
Set<String> baldridgeSetWithLegalBasesFilter = entityLogMongoService.findEntryIdsByValueAndEngineManualWithFilters(baldridgeValue,
Set.of("CBI_author", "Test"),
Set.of("Test"),
Collections.emptySet());
assertEquals(baldridgeSetWithLegalBasesFilter.size(), 1);
}
}

View File

@ -66,7 +66,9 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemp
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.RecategorizationBulkLocalRequestModel;
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.RemoveRedactionBulkLocalRequestModel;
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.Point;
@ -1736,26 +1738,34 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
entityLogMongoService.upsertEntityLog(file.getDossierId(), file.getFileId(), entityLog);
ManualRedactionResponse forceResponse = manualRedactionClient.forceRedactionBulk(dossier.getId(),
file.getId(),
Set.of(ForceRedactionRequestModel.builder().annotationId("forceRedactionAnnotation").comment("comment").legalBasis("1").build()));
file.getId(),
Set.of(ForceRedactionRequestModel.builder()
.annotationId("forceRedactionAnnotation")
.comment("comment")
.legalBasis("1")
.build()));
var allManualRedactions = manualRedactionClient.getManualRedactions(dossier.getId(), file.getId(), false, true);
assertEquals(allManualRedactions.getForceRedactions().size(), 1);
assertFalse(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals(forceResponse.getManualAddResponses().get(0).getAnnotationId())));
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId()
.equals(forceResponse.getManualAddResponses()
.get(0).getAnnotationId())));
var unprocessedManualRedactions = manualRedactionClient.getManualRedactions(dossier.getId(), file.getId(), true, true);
assertEquals(unprocessedManualRedactions.getForceRedactions().size(), 1);
assertFalse(unprocessedManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation")));
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals(forceResponse.getManualAddResponses().get(0).getAnnotationId())));
.anyMatch(entry -> entry.getAnnotationId()
.equals(forceResponse.getManualAddResponses()
.get(0).getAnnotationId())));
fileProcessingClient.analysisSuccessful(dossier.getId(),
file.getId(),
@ -1768,32 +1778,42 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
.build());
ManualRedactionResponse forceResponse2 = manualRedactionClient.forceRedactionBulk(dossier.getId(),
file.getId(),
Set.of(ForceRedactionRequestModel.builder().annotationId("forceRedactionAnnotation2").comment("comment").legalBasis("1").build()));
file.getId(),
Set.of(ForceRedactionRequestModel.builder()
.annotationId("forceRedactionAnnotation2")
.comment("comment")
.legalBasis("1")
.build()));
allManualRedactions = manualRedactionClient.getManualRedactions(dossier.getId(), file.getId(), false, true);
assertEquals(allManualRedactions.getForceRedactions().size(), 2);
assertFalse(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals(forceResponse.getManualAddResponses().get(0).getAnnotationId())));
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId()
.equals(forceResponse.getManualAddResponses()
.get(0).getAnnotationId())));
assertFalse(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation2")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals(forceResponse2.getManualAddResponses().get(0).getAnnotationId())));
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation2")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId()
.equals(forceResponse2.getManualAddResponses()
.get(0).getAnnotationId())));
unprocessedManualRedactions = manualRedactionClient.getManualRedactions(dossier.getId(), file.getId(), true, true);
assertEquals(unprocessedManualRedactions.getForceRedactions().size(), 1);
assertFalse(unprocessedManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation2")));
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation2")));
assertTrue(unprocessedManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals(forceResponse2.getManualAddResponses().get(0).getAnnotationId())));
.anyMatch(entry -> entry.getAnnotationId()
.equals(forceResponse2.getManualAddResponses()
.get(0).getAnnotationId())));
fileProcessingClient.analysisSuccessful(dossier.getId(),
file.getId(),
@ -1812,13 +1832,17 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals(forceResponse.getManualAddResponses().get(0).getAnnotationId())));
.anyMatch(entry -> entry.getAnnotationId()
.equals(forceResponse.getManualAddResponses()
.get(0).getAnnotationId())));
assertFalse(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals("forceRedactionAnnotation2")));
assertTrue(allManualRedactions.getForceRedactions()
.stream()
.anyMatch(entry -> entry.getAnnotationId().equals(forceResponse2.getManualAddResponses().get(0).getAnnotationId())));
.anyMatch(entry -> entry.getAnnotationId()
.equals(forceResponse2.getManualAddResponses()
.get(0).getAnnotationId())));
unprocessedManualRedactions = manualRedactionClient.getManualRedactions(dossier.getId(), file.getId(), true, true);
assertTrue(unprocessedManualRedactions.getForceRedactions().isEmpty());
@ -2576,10 +2600,12 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
List.of(entityLogEntry),
List.of(new EntityLogLegalBasis("1.1 personal data (incl. geolocation); Article 39(e)(3)",
"desc",
"Article 39(e)(3) of Regulation (EC) No 178/2002",""),
"Article 39(e)(3) of Regulation (EC) No 178/2002",
""),
new EntityLogLegalBasis("4. commercial information",
"desc 2",
"Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)","")),
"Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)",
"")),
0,
0,
0,
@ -2646,10 +2672,12 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
List.of(entityLogEntry),
List.of(new EntityLogLegalBasis("1.1 personal data (incl. geolocation); Article 39(e)(3)",
"desc",
"Article 39(e)(3) of Regulation (EC) No 178/2002", ""),
"Article 39(e)(3) of Regulation (EC) No 178/2002",
""),
new EntityLogLegalBasis("4. commercial information",
"desc 2",
"Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)","")),
"Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)",
"")),
0,
0,
0,
@ -3186,4 +3214,122 @@ public class ManualRedactionTest extends AbstractPersistenceServerServiceTest {
assertTrue(result.getMessage().contains("Maximum number of remove from dictionary requests is 100."));
}
@Test
public void testBulkLocal() {
var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate);
var file = fileTesterAndProvider.testAndProvideFile(dossier);
var type1 = typeProvider.testAndProvideType(dossierTemplate, dossier, "test1", false, 70);
var type2 = typeProvider.testAndProvideType(dossierTemplate, dossier, "test2", false, 71);
var type3 = typeProvider.testAndProvideType(dossierTemplate, dossier, "test3", false, 72);
var newType = typeProvider.testAndProvideType(dossierTemplate, dossier, "newType", false, 73);
List<EntityLogEntry> entityLogEntries = new ArrayList<>();
String legal3 = "Legal 3";
String darthVader = "Darth Vader";
for (int i = 0; i < 101; i++) {
entityLogEntries.add(EntityLogEntry.builder()
.id("AnnotationId1" + i)
.type(type1.getType())
.value("Luke Skywalker" + i)
.entryType(EntryType.ENTITY)
.state(EntryState.APPLIED)
.section("section")
.legalBasis("Legal 1")
.engines(Set.of(Engine.MANUAL))
.build());
entityLogEntries.add(EntityLogEntry.builder()
.id("AnnotationId2" + i)
.type(type2.getType())
.value(darthVader)
.entryType(EntryType.ENTITY)
.state(EntryState.APPLIED)
.section("section")
.legalBasis("Legal 2")
.engines(Set.of(Engine.MANUAL))
.build());
entityLogEntries.add(EntityLogEntry.builder()
.id("AnnotationId3" + i)
.type(type3.getType())
.value(darthVader)
.entryType(EntryType.ENTITY)
.state(EntryState.APPLIED)
.section("section")
.legalBasis(legal3)
.engines(Set.of(Engine.MANUAL))
.build());
}
var entityLog = new EntityLog(1, 1, entityLogEntries, null, 0, 0, 0, 0);
fileManagementStorageService.saveEntityLog(dossier.getId(), file.getId(), entityLog);
when(entityLogService.getEntityLog(any(), any(), anyBoolean())).thenReturn(entityLog);
String newLegal = "new Legal";
String otherSection = "other section";
ManualRedactionResponse manualRedactionResponse = manualRedactionClient.recategorizeBulkLocal(dossier.getId(),
file.getId(),
RecategorizationBulkLocalRequestModel.builder()
.rectangle(false)
.type(newType.getType())
.legalBasis(newLegal)
.section(otherSection)
.value("Luke Skywalker37")
.build(),
false);
assertEquals(manualRedactionResponse.getManualAddResponses().size(), 1);
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(0).getEntityLogEntry().getType(), newType.getType());
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(0).getEntityLogEntry().getLegalBasis(), newLegal);
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(0).getEntityLogEntry().getSection(), otherSection);
manualRedactionResponse = manualRedactionClient.recategorizeBulkLocal(dossier.getId(),
file.getId(),
RecategorizationBulkLocalRequestModel.builder()
.rectangle(false)
.type(newType.getType())
.legalBasis(newLegal)
.section(otherSection)
.value(darthVader)
.originLegalBases(Set.of(legal3))
.build(),
false);
assertEquals(manualRedactionResponse.getManualAddResponses().size(), 101);
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(1).getEntityLogEntry().getType(), newType.getType());
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(29).getEntityLogEntry().getLegalBasis(), newLegal);
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(79).getEntityLogEntry().getSection(), otherSection);
manualRedactionResponse = manualRedactionClient.recategorizeBulkLocal(dossier.getId(),
file.getId(),
RecategorizationBulkLocalRequestModel.builder()
.rectangle(false)
.type(newType.getType())
.legalBasis(newLegal)
.section(otherSection)
.value(darthVader)
.originTypes(Set.of(type2.getType()))
.build(),
false);
assertEquals(manualRedactionResponse.getManualAddResponses().size(), 101);
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(0).getEntityLogEntry().getType(), newType.getType());
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(36).getEntityLogEntry().getLegalBasis(), newLegal);
assertEquals(manualRedactionResponse.getManualAddResponses()
.get(98).getEntityLogEntry().getSection(), otherSection);
manualRedactionResponse = manualRedactionClient.removeRedactionBulkLocal(dossier.getId(),
file.getId(),
RemoveRedactionBulkLocalRequestModel.builder().rectangle(false).value(darthVader).build(),
false);
assertEquals(manualRedactionResponse.getManualAddResponses().size(), 202);
}
}

View File

@ -0,0 +1,150 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.pdftron.redaction.v1.api.model.RedactionResultMessage;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DownloadClient;
import com.iqser.red.service.peristence.v1.server.integration.client.FileClient;
import com.iqser.red.service.peristence.v1.server.integration.client.ReportTemplateClient;
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.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.model.DownloadJob;
import com.iqser.red.service.persistence.management.v1.processor.service.download.DownloadCompressionMessageReceiver;
import com.iqser.red.service.persistence.management.v1.processor.service.download.DownloadReportMessageReceiver;
import com.iqser.red.service.persistence.management.v1.processor.service.download.RedactionResultMessageReceiver;
import com.iqser.red.service.persistence.management.v1.processor.service.job.DownloadReadyJob;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.PendingEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.annotationentity.ManualRedactionRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.queue.SearchTermOccurrencesResponseReceiver;
import com.iqser.red.service.persistence.service.v1.api.shared.model.BulkLocalResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PrepareDownloadWithOptionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
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.DownloadFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplate;
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.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.download.DownloadStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.download.DownloadStatusValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionBulkLocalRequestModel;
import com.iqser.red.service.redaction.report.v1.api.model.ReportResultMessage;
import com.iqser.red.service.redaction.report.v1.api.model.StoredFileInformation;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults;
@FieldDefaults(level = AccessLevel.PRIVATE)
public class SearchTermOccurrencesTest extends AbstractPersistenceServerServiceTest {
@Autowired
FileTesterAndProvider fileTesterAndProvider;
@Autowired
DossierTesterAndProvider dossierTesterAndProvider;
@Autowired
DossierTemplateTesterAndProvider dossierTemplateTesterAndProvider;
@Autowired
TypeProvider typeProvider;
@Autowired
SearchTermOccurrencesResponseReceiver searchTermOccurrencesResponseReceiver;
@Autowired
FileStatusPersistenceService fileStatusPersistenceService;
@Autowired
ManualRedactionRepository manualRedactionRepository;
@Autowired
PendingEntryFactory pendingEntryFactory;
@Test
public void testBulkLocal() {
var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate);
var file = fileTesterAndProvider.testAndProvideFile(dossier);
var type = typeProvider.testAndProvideType(dossierTemplate, dossier, "test", false, 70);
fileStatusPersistenceService.updateProcessingStatus(file.getFileId(), ProcessingStatus.BULK_LOCAL_REDACTIONS_PROCESSING);
searchTermOccurrencesResponseReceiver.receive(BulkLocalResponse.builder()
.fileId(file.getId())
.dossierId(dossier.getId())
.searchTerm("searchTerm")
.type(type.getType())
.legalBasis("legalBasis")
.reason("reason")
.section("section")
.positions(List.of(List.of(new Position(new float[]{1f, 2f, 3f, 4f}, 1))))
.build());
List<ManualRedactionEntryEntity> newEntries = manualRedactionRepository.findByFileIdAndOptions(file.getId(), false, false, false);
assertEquals(newEntries.size(), 1);
assertEquals(newEntries.get(0).getValue(), "searchTerm");
assertEquals(newEntries.get(0).getLegalBasis(), "legalBasis");
assertEquals(newEntries.get(0).getReason(), "reason");
assertEquals(newEntries.get(0).getSection(), "section");
assertEquals(fileStatusPersistenceService.getStatus(file.getId()).getProcessingStatus(), ProcessingStatus.PROCESSED);
}
@Test
public void testPendingEntryCreation() {
AddRedactionBulkLocalRequestModel addRedactionBulkLocalRequestModel = AddRedactionBulkLocalRequestModel.builder()
.value("Max")
.type("123")
.legalBasis("legalbasis")
.reason("reason")
.section("section")
.positions(List.of(new Rectangle(333.92593f, 446.8f, 19.152008f, 11.81175f, 1)))
.build();
EntityLogEntry entityLogEntry = pendingEntryFactory.buildAddRedactionBulkLocalEntry(addRedactionBulkLocalRequestModel);
List<Position> positions = convertPositions(addRedactionBulkLocalRequestModel.getPositions());
assertEquals(entityLogEntry.getPositions().get(0), positions.get(0));
assertEquals(entityLogEntry.getValue(), addRedactionBulkLocalRequestModel.getValue());
assertEquals(entityLogEntry.getLegalBasis(), addRedactionBulkLocalRequestModel.getLegalBasis());
assertEquals(entityLogEntry.getReason(), addRedactionBulkLocalRequestModel.getReason());
assertEquals(entityLogEntry.getSection(), addRedactionBulkLocalRequestModel.getSection());
}
private List<Position> convertPositions(List<Rectangle> rectangles) {
return rectangles.stream()
.map(rectangle -> new Position(rectangle.getTopLeftX(), rectangle.getTopLeftY(), rectangle.getWidth(), rectangle.getHeight(), rectangle.getPage()))
.collect(Collectors.toList());
}
}

View File

@ -38,5 +38,7 @@ public class AnalyzeRequest {
@Builder.Default
private List<ComponentMappingMetadata> componentMappings = new ArrayList<>();
private BulkLocalRequest bulkLocalRequest;
}

View File

@ -0,0 +1,34 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model;
import java.util.Collections;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BulkLocalRequest {
@NonNull
private String searchTerm;
@NonNull
private String type;
@NonNull
private String reason;
private String legalBasis;
private String section;
@Builder.Default
private Set<Integer> pageNumbers = Collections.emptySet();
}

View File

@ -0,0 +1,43 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BulkLocalResponse {
@NonNull
private String dossierId;
@NonNull
private String fileId;
@NonNull
private String searchTerm;
@NonNull
private String type;
@NonNull
private String reason;
private String legalBasis;
private String section;
@NonNull
@Builder.Default
private List<List<Position>> positions = new ArrayList<>();
}

View File

@ -5,5 +5,6 @@ public enum MessageType {
ANALYSE,
REANALYSE,
SURROUNDING_TEXT_ANALYSIS,
IMPORTED_REDACTIONS_ONLY
IMPORTED_REDACTIONS_ONLY,
SEARCH_BULK_LOCAL_TERM
}

View File

@ -19,5 +19,6 @@ public enum ProcessingStatus {
PRE_PROCESSED,
FIGURE_DETECTION_ANALYZING,
TABLE_PARSING_ANALYZING,
VISUAL_LAYOUT_PARSING_ANALYZING
VISUAL_LAYOUT_PARSING_ANALYZING,
BULK_LOCAL_REDACTIONS_PROCESSING
}

View File

@ -0,0 +1,43 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.manual;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
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.type.DictionaryEntryType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AddRedactionBulkLocalRequestModel {
@NonNull
private String type;
@NonNull
private String value;
@NonNull
private String reason;
private String legalBasis;
private String section;
@NonNull
@Builder.Default
private List<Rectangle> positions = new ArrayList<>();
@Builder.Default
private Set<Integer> pageNumbers = Collections.emptySet();
}

View File

@ -0,0 +1,29 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.manual;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ForceRedactionBulkLocalRequestModel {
@NonNull
private String annotationId;
@NonNull
private String value;
private String legalBasis;
private boolean rectangle;
private Position position;
}

View File

@ -0,0 +1,39 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.manual;
import java.util.Collections;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RecategorizationBulkLocalRequestModel {
private String value;
private String type;
private String legalBasis;
private String section;
private boolean rectangle;
private Position position;
@Builder.Default
private Set<String> originTypes = Collections.emptySet();
@Builder.Default
private Set<String> originLegalBases = Collections.emptySet();
@Builder.Default
private Set<Integer> pageNumbers = Collections.emptySet();
}

View File

@ -0,0 +1,36 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.manual;
import java.util.Collections;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RemoveRedactionBulkLocalRequestModel {
private String value;
private boolean rectangle;
private Position position;
@Builder.Default
private Set<String> originTypes = Collections.emptySet();
@Builder.Default
private Set<String> originLegalBases = Collections.emptySet();
@Builder.Default
private Set<Integer> pageNumbers = Collections.emptySet();
}

View File

@ -0,0 +1,13 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import java.util.List;
import java.util.Set;
public interface EntityLogEntryDocumentCustomRepository {
List<String> findEntryIdsByValueAndEngineManualWithFilters(String value, Set<String> originTypes, Set<String> originLegalBases, Set<Integer> pageNumbers);
List<String> findEntryIdsByMatchingPositionAndEngineManualWithFilters(float[] rectangle, Set<String> originTypes, Set<String> originLegalBases, Set<Integer> pageNumbers);
}

View File

@ -0,0 +1,95 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Repository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.EntityLogEntryDocument;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;
@Repository
@AllArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class EntityLogEntryDocumentCustomRepositoryImpl implements EntityLogEntryDocumentCustomRepository {
MongoTemplate mongoTemplate;
@Override
public List<String> findEntryIdsByValueAndEngineManualWithFilters(String value, Set<String> originTypes, Set<String> originLegalBases, Set<Integer> pageNumbers) {
Query query = new Query();
List<Criteria> criteriaList = new ArrayList<>();
criteriaList.add(Criteria.where("value").is(value));
addCommonCriteria(originTypes, originLegalBases, pageNumbers, criteriaList);
Criteria mergedCriteria = new Criteria().andOperator(criteriaList.toArray(new Criteria[0]));
query.addCriteria(mergedCriteria);
return executeQuery(query);
}
@Override
public List<String> findEntryIdsByMatchingPositionAndEngineManualWithFilters(float[] rectangle,
Set<String> originTypes,
Set<String> originLegalBases,
Set<Integer> pageNumbers) {
Query query = new Query();
List<Criteria> criteriaList = new ArrayList<>();
criteriaList.add(Criteria.where("positions").elemMatch(Criteria.where("rectangle").is(rectangle)));
addCommonCriteria(originTypes, originLegalBases, pageNumbers, criteriaList);
Criteria mergedCriteria = new Criteria().andOperator(criteriaList.toArray(new Criteria[0]));
query.addCriteria(mergedCriteria);
return executeQuery(query);
}
@NotNull
private void addCommonCriteria(Set<String> originTypes, Set<String> originLegalBases, Set<Integer> pageNumbers, List<Criteria> criteriaList) {
criteriaList.add(Criteria.where("engines").is("MANUAL"));
if (originTypes != null && !originTypes.isEmpty()) {
criteriaList.add(Criteria.where("type").in(originTypes));
}
if (originLegalBases != null && !originLegalBases.isEmpty()) {
criteriaList.add(Criteria.where("legalBasis").in(originLegalBases));
}
if (pageNumbers != null && !pageNumbers.isEmpty()) {
Criteria positionsCriteria = Criteria.where("positions").elemMatch(Criteria.where("pageNumber").in(pageNumbers));
criteriaList.add(positionsCriteria);
}
}
private List<String> executeQuery(Query query) {
query.fields().include("entryId").exclude("id");
return mongoTemplate.find(query, EntityLogEntryDocument.class)
.stream()
.map(EntityLogEntryDocument::getEntryId)
.collect(Collectors.toList());
}
}

View File

@ -68,4 +68,16 @@ public interface EntityLogEntryDocumentRepository extends MongoRepository<Entity
@Query(value = "{ 'entityLogId': ?0, 'engines': 'IMPORTED' }", exists = true)
boolean existsByEntityLogIdAndEngineImported(String entityLogId);
@Query(value = "{ 'value': ?0, 'engines': 'MANUAL' }", fields = "{ 'entryId': 1, 'id': 0 }")
List<String> findEntryIdsByValueAndEngineManual(String value);
@Query(value = "{ 'positions': { $elemMatch: { 'pageNumber': ?0, 'rectangle': ?1 } }, 'engines': 'MANUAL' }", fields = "{ 'entryId': 1, 'id': 0 }")
List<String> findEntryIdsByMatchingFullPositionAndEngineManual(int pageNumber, float[] rectangle);
@Query(value = "{ 'positions': { $elemMatch: { 'rectangle': ?0 } }, 'engines': 'MANUAL' }", fields = "{ 'entryId': 1, 'id': 0 }")
List<String> findEntryIdsByMatchingPositionAndEngineManual(float[] rectangle);
}

View File

@ -2,6 +2,7 @@ package com.iqser.red.service.persistence.service.v1.api.shared.mongo.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@ -16,27 +17,23 @@ import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.En
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.exception.DocumentNotFoundException;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.EntityLogDocumentMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogEntryDocumentCustomRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.EntityLogEntryDocumentRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class EntityLogMongoService {
private final EntityLogDocumentRepository entityLogDocumentRepository;
private final EntityLogEntryDocumentRepository entityLogEntryDocumentRepository;
private final EntityLogDocumentUpdateService entityLogDocumentUpdateService;
private final EntityLogEntryDocumentCustomRepository entityLogEntryDocumentCustomRepository;
private final EntityLogDocumentMapper mapper = EntityLogDocumentMapper.INSTANCE;
public EntityLogMongoService(EntityLogDocumentRepository entityLogDocumentRepository,
EntityLogEntryDocumentRepository entityLogEntryDocumentRepository,
EntityLogDocumentUpdateService entityLogDocumentUpdateService) {
this.entityLogDocumentRepository = entityLogDocumentRepository;
this.entityLogEntryDocumentRepository = entityLogEntryDocumentRepository;
this.entityLogDocumentUpdateService = entityLogDocumentUpdateService;
}
public void insertEntityLog(String dossierId, String fileId, EntityLog entityLog) {
EntityLogDocument entityLogDocument = entityLogDocumentRepository.insert(mapper.toLogDocument(dossierId, fileId, entityLog));
@ -340,4 +337,34 @@ public class EntityLogMongoService {
return entityLogEntryDocumentRepository.existsByEntityLogIdAndEngineImported(entityLogId);
}
public Set<String> findEntryIdsOfManualsWithValue(String value) {
return new HashSet<>(entityLogEntryDocumentRepository.findEntryIdsByValueAndEngineManual(value));
}
public Set<String> findEntryIdsOfManualsWithPositionRectangle(float[] rectangle) {
return new HashSet<>(entityLogEntryDocumentRepository.findEntryIdsByMatchingPositionAndEngineManual(rectangle));
}
public Set<String> findEntryIdsByValueAndEngineManualWithFilters(String value, Set<String> originTypes, Set<String> originLegalBases, Set<Integer> pageNumbers) {
return new HashSet<>(entityLogEntryDocumentCustomRepository.findEntryIdsByValueAndEngineManualWithFilters(value, originTypes, originLegalBases, pageNumbers));
}
public Set<String> findEntryIdsByMatchingFullPositionAndEngineManualWithFilters(float[] rectangle,
Set<String> originTypes,
Set<String> originLegalBases,
Set<Integer> pageNumbers) {
return new HashSet<>(entityLogEntryDocumentCustomRepository.findEntryIdsByMatchingPositionAndEngineManualWithFilters(rectangle,
originTypes,
originLegalBases,
pageNumbers));
}
}