RED-8339: Component Overrides in DocuMine

This commit is contained in:
Ali Oezyetimoglu 2024-05-13 22:39:18 +02:00
parent 9e70d9e199
commit 9079ae18af
30 changed files with 1431 additions and 825 deletions

View File

@ -3,30 +3,21 @@ package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_RSS;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_REDACTION_LOG;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentOverrideService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ComponentLogResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@ -35,15 +26,15 @@ import lombok.experimental.FieldDefaults;
@RestController
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@Deprecated(forRemoval = true)
public class ComponentLogController implements ComponentLogResource {
ComponentLogService componentLogService;
ComponentOverrideService componentOverrideService;
AuditPersistenceService auditPersistenceService;
AccessControlService accessControlService;
@Override
@Deprecated(forRemoval = true)
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ComponentLog getComponentLog(String dossierId, String fileId, boolean includeOverrides) {
@ -54,134 +45,42 @@ public class ComponentLogController implements ComponentLogResource {
}
@PostMapping(value = COMPONENT_LOG_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides) {
public RedirectView addOverride(String dossierTemplateId,
@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody ComponentLogEntry override) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
if (componentsOverrides.getComponentOverrides() == null || componentsOverrides.getComponentOverrides().isEmpty()) {
throw new BadRequestException("Request body cannot be empty!");
}
var componentLog = componentLogService.getComponentLog(dossierId, fileId);
var allComponents = componentLog.getComponentLogEntries();
componentOverrideService.addOverrides(dossierId, fileId, componentsOverrides);
componentsOverrides.getComponentOverrides()
.forEach((componentName, overrideValue) -> auditOverride(dossierId, fileId, componentName, overrideValue, allComponents));
return new RedirectView(String.format("/api/dossier-templates/{%s}/dossiers/{%s}/files/{%s}/overrides", dossierTemplateId, dossierId, fileId), true);
}
@GetMapping(value = COMPONENT_LOG_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
public RedirectView getOverrides(String dossierTemplateId, @PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
return componentOverrideService.getOverrides(dossierId, fileId);
return new RedirectView(String.format("/api/dossier-templates/{%s}/dossiers/{%s}/files/{%s}/overrides", dossierTemplateId, dossierId, fileId), true);
}
@PostMapping(value = COMPONENT_LOG_PATH + OVERRIDE_PATH + "/revert" + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest) {
public RedirectView revertOverrides(String dossierTemplateId,
@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody RevertOverrideRequest revertOverrideRequest) {
accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
accessControlService.validateFileResourceExistence(fileId);
if (revertOverrideRequest.getComponents() == null || revertOverrideRequest.getComponents().isEmpty()) {
throw new BadRequestException("Request body cannot be empty!");
}
var componentLog = componentLogService.getComponentLog(dossierId, fileId);
var allComponents = componentLog.getComponentLogEntries();
componentOverrideService.revertOverrides(dossierId, fileId, revertOverrideRequest);
revertOverrideRequest.getComponents()
.forEach(componentNameToRevert -> auditOverrideRevert(dossierId, fileId, componentNameToRevert, allComponents));
}
private void auditOverride(String dossierId, String fileId, String componentName, String overrideValue, List<ComponentLogEntry> allComponentLogEntries) {
Optional<ComponentLogEntry> component = allComponentLogEntries.stream()
.filter(c -> c.getName().equals(componentName))
.findFirst();
String originalValue = getOriginalValue(component);
String value = getValue(component);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component is overwritten with value")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
componentName,
"Action",
"MODIFY",
"OriginalValue",
originalValue,
"OldValue",
value,
"NewValue",
overrideValue))
.build());
}
private void auditOverrideRevert(String dossierId, String fileId, String componentNameToRevert, List<ComponentLogEntry> allComponentLogEntries) {
Optional<ComponentLogEntry> component = allComponentLogEntries.stream()
.filter(c -> c.getName().equals(componentNameToRevert))
.findFirst();
String originalValue = getOriginalValue(component);
String value = getValue(component);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component override for was reverted")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
componentNameToRevert,
"Action",
"REVERT",
"OriginalValue",
originalValue,
"OldValue",
value,
"NewValue",
originalValue))
.build());
}
private String getValue(Optional<ComponentLogEntry> component) {
return component.map(ComponentLogEntry::getComponentValues)
.stream()
.map(a -> a.stream()
.map(ComponentLogEntryValue::getValue)
.collect(Collectors.joining(", ")))
.findFirst()
.orElse("");
}
private static String getOriginalValue(Optional<ComponentLogEntry> component) {
return component.map(ComponentLogEntry::getComponentValues)
.stream()
.map(a -> a.stream()
.map(ComponentLogEntryValue::getOriginalValue)
.collect(Collectors.joining(", ")))
.findFirst()
.orElse("");
return new RedirectView(String.format("/api/dossier-templates/{%s}/dossiers/{%s}/files/{%s}/overrides/revert", dossierTemplateId, dossierId, fileId), true);
}
}

View File

@ -1,287 +0,0 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_RSS;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentOverrideService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RSSResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntityReference;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSFileResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSFileResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.SCMComponent;
import com.iqser.red.service.redaction.report.v1.api.model.rss.ScmAnnotation;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
@Deprecated(forRemoval = true)
@RestController
@RequiredArgsConstructor
@ConditionalOnProperty(name = "application.rss.component-log.enabled", havingValue = "true")
public class RSSComponentLogController implements RSSResource {
private final ComponentOverrideService componentOverrideService;
private final AuditPersistenceService auditPersistenceService;
private final ComponentLogService componentLogService;
private final StatusController statusController;
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public RSSResponse getRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
List<FileStatus> dossierFiles;
if (StringUtils.isBlank(fileId)) {
dossierFiles = statusController.getDossierStatus(dossierId);
} else {
dossierFiles = List.of(statusController.getFileStatus(dossierId, fileId));
}
List<RSSFileResponse> fileResponses = dossierFiles.stream()
.map(this::getRssResponse)
.toList();
return new RSSResponse(fileResponses);
}
private RSSFileResponse getRssResponse(FileStatus file) {
var componentLog = componentLogService.getComponentLog(file.getDossierId(), file.getId(), true);
Map<String, String> results = new LinkedHashMap<>();
componentLog.getComponentLogEntries()
.forEach(entry -> {
if (entry.getComponentValues().size() <= 1) {
results.put(entry.getName(),
entry.getComponentValues()
.get(0).getValue());
return;
}
List<ComponentLogEntryValue> componentValues = entry.getComponentValues();
for (int i = 0, componentValuesSize = componentValues.size(); i < componentValuesSize; i++) {
ComponentLogEntryValue v = componentValues.get(i);
results.put(entry.getName() + "_" + (i + 1), v.getValue());
}
});
return RSSFileResponse.builder().filename(file.getFilename()).result(results).build();
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public DetailedRSSResponse getDetailedRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
List<FileStatus> dossierFiles;
if (StringUtils.isBlank(fileId)) {
dossierFiles = statusController.getDossierStatus(dossierId);
} else {
dossierFiles = List.of(statusController.getFileStatus(dossierId, fileId));
}
List<DetailedRSSFileResponse> fileResponses = dossierFiles.stream()
.map(this::getDetailedRssResponse)
.toList();
return new DetailedRSSResponse(fileResponses);
}
private DetailedRSSFileResponse getDetailedRssResponse(FileStatus file) {
var componentLog = componentLogService.getComponentLog(file.getDossierId(), file.getId(), true);
Map<String, SCMComponent> results = new LinkedHashMap<>();
componentLog.getComponentLogEntries()
.forEach(entry -> {
if (entry.getComponentValues().size() <= 1) {
results.put(entry.getName(),
toSCMComponent(entry.getComponentValues()
.get(0)));
return;
}
List<ComponentLogEntryValue> componentValues = entry.getComponentValues();
for (int i = 0, componentValuesSize = componentValues.size(); i < componentValuesSize; i++) {
ComponentLogEntryValue v = componentValues.get(i);
results.put(entry.getName() + "_" + (i + 1), toSCMComponent(v));
}
});
return DetailedRSSFileResponse.builder().filename(file.getFilename()).result(results).build();
}
private SCMComponent toSCMComponent(ComponentLogEntryValue v) {
return SCMComponent.builder()
.value(v.getValue().equals(v.getOriginalValue()) ? null : v.getValue())
.originalValue(v.getOriginalValue())
.transformation(v.getValueDescription())
.scmAnnotations(v.getComponentLogEntityReferences()
.stream()
.map(this::toScmAnnotation)
.toList())
.build();
}
private ScmAnnotation toScmAnnotation(ComponentLogEntityReference er) {
return ScmAnnotation.builder().type(er.getType()).pages(Set.of(er.getPage())).ruleIdentifier(er.getEntityRuleId()).reason(formatType(er.getType())).build();
}
private static String formatType(String type) {
return type.substring(0, 1).toUpperCase(Locale.ENGLISH) + type.substring(1).toLowerCase(Locale.ENGLISH).replaceAll("_", " ");
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides) {
var componentLog = componentLogService.getComponentLog(dossierId, fileId);
var allComponents = componentLog.getComponentLogEntries();
componentOverrideService.addOverrides(dossierId, fileId, componentsOverrides);
componentsOverrides.getComponentOverrides()
.forEach((componentName, overrideValue) -> auditOverride(dossierId, fileId, componentName, overrideValue, allComponents));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
return componentOverrideService.getOverrides(dossierId, fileId);
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest) {
var componentLog = componentLogService.getComponentLog(dossierId, fileId);
var allComponents = componentLog.getComponentLogEntries();
componentOverrideService.revertOverrides(dossierId, fileId, revertOverrideRequest);
revertOverrideRequest.getComponents()
.forEach(componentNameToRevert -> auditOverrideRevert(dossierId, fileId, componentNameToRevert, allComponents));
}
private void auditOverride(String dossierId, String fileId, String componentName, String overrideValue, List<ComponentLogEntry> allComponentLogEntries) {
Optional<ComponentLogEntry> component = allComponentLogEntries.stream()
.filter(c -> c.getName().equals(componentName))
.findFirst();
String originalValue = getOriginalValue(component);
String value = getValue(component);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component is overwritten with value")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
componentName,
"Action",
"MODIFY",
"OriginalValue",
originalValue,
"OldValue",
value,
"NewValue",
overrideValue))
.build());
}
private void auditOverrideRevert(String dossierId, String fileId, String componentNameToRevert, List<ComponentLogEntry> allComponentLogEntries) {
Optional<ComponentLogEntry> component = allComponentLogEntries.stream()
.filter(c -> c.getName().equals(componentNameToRevert))
.findFirst();
String originalValue = getOriginalValue(component);
String value = getValue(component);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component override for was reverted")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
componentNameToRevert,
"Action",
"REVERT",
"OriginalValue",
originalValue,
"OldValue",
value,
"NewValue",
originalValue))
.build());
}
private String getValue(Optional<ComponentLogEntry> component) {
return component.map(ComponentLogEntry::getComponentValues)
.stream()
.map(a -> a.stream()
.map(ComponentLogEntryValue::getValue)
.collect(Collectors.joining(", ")))
.findFirst()
.orElse("");
}
private static String getOriginalValue(Optional<ComponentLogEntry> component) {
return component.map(ComponentLogEntry::getComponentValues)
.stream()
.map(a -> a.stream()
.map(ComponentLogEntryValue::getOriginalValue)
.collect(Collectors.joining(", ")))
.findFirst()
.orElse("");
}
}

View File

@ -1,148 +0,0 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_RSS;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.RssReportClient;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentOverrideService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RSSResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSFileResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSResponse;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
@Deprecated(forRemoval = true)
@RestController
@RequiredArgsConstructor
@ConditionalOnProperty(name = "application.rss.component-log.enabled", havingValue = "false")
public class RSSController implements RSSResource {
private final RssReportClient rssReportClient;
private final ComponentOverrideService componentOverrideService;
private final AuditPersistenceService auditPersistenceService;
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public RSSResponse getRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
return convert(rssReportClient.getRSS(dossierId, fileId));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
private RSSResponse convert(com.iqser.red.service.redaction.report.v1.api.model.rss.RSSResponse rssResponse) {
return new RSSResponse(rssResponse.getFiles()
.stream()
.map(this::convert)
.collect(Collectors.toList()));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
private RSSFileResponse convert(com.iqser.red.service.redaction.report.v1.api.model.rss.RSSFileResponse rssFileResponse) {
return new RSSFileResponse(rssFileResponse.getFilename(), rssFileResponse.getResult());
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public DetailedRSSResponse getDetailedRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
return rssReportClient.getDetailedRSS(dossierId, fileId);
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides) {
var rssReport = rssReportClient.getDetailedRSS(dossierId, fileId);
var components = rssReport.getFiles()
.get(0).getResult();
componentOverrideService.addOverrides(dossierId, fileId, componentsOverrides);
componentsOverrides.getComponentOverrides()
.forEach((key, value) -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component is overwritten with value")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
key,
"Action",
"MODIFY",
"OriginalValue",
components.get(key).getOriginalValue(),
"OldValue",
components.get(key).getValue() != null ? components.get(key)
.getValue() : components.get(key).getOriginalValue(),
"NewValue",
value))
.build()));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
return componentOverrideService.getOverrides(dossierId, fileId);
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest) {
var rssReport = rssReportClient.getDetailedRSS(dossierId, fileId);
var components = rssReport.getFiles()
.get(0).getResult();
componentOverrideService.revertOverrides(dossierId, fileId, revertOverrideRequest);
revertOverrideRequest.getComponents()
.forEach(component -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component override for was reverted")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
component,
"Action",
"REVERT",
"OriginalValue",
components.get(component).getOriginalValue(),
"OldValue",
components.get(component).getValue() != null ? components.get(component)
.getValue() : components.get(component).getOriginalValue(),
"NewValue",
components.get(component).getOriginalValue()))
.build()));
}
}

View File

@ -1,27 +1,25 @@
package com.iqser.red.persistence.service.v2.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_RSS;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource.DOSSIER_ID_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource.DOSSIER_TEMPLATE_ID_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.FileResource.FILE_ID_PARAM;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierTemplateController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.StatusController;
import com.iqser.red.persistence.service.v2.external.api.impl.mapper.ComponentMapper;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntityReference;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.Component;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentValue;
import com.iqser.red.service.persistence.service.v2.api.external.model.EntityReference;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentOverrideModelList;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponents;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponentsList;
import com.iqser.red.service.persistence.service.v2.api.external.resource.ComponentResource;
@ -37,10 +35,12 @@ import lombok.experimental.FieldDefaults;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ComponentControllerV2 implements ComponentResource {
DossierTemplateController dossierTemplateController;
ComponentLogService componentLogService;
StatusController statusController;
FileStatusService fileStatusService;
DossierTemplatePersistenceService dossierTemplatePersistenceService;
ComponentMapper componentMapper = ComponentMapper.INSTANCE;
@Override
@ -52,65 +52,7 @@ public class ComponentControllerV2 implements ComponentResource {
dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
var componentLog = componentLogService.getComponentLog(dossierId, fileId, true);
Map<String, List<String>> basicComponent = new LinkedHashMap<>();
for (ComponentLogEntry componentLogEntry : componentLog.getComponentLogEntries()) {
basicComponent.put(componentLogEntry.getName(),
componentLogEntry.getComponentValues()
.stream()
.map(ComponentLogEntryValue::getValue)
.toList());
}
Map<String, Component> componentsDetails = new LinkedHashMap<>();
if (includeDetails) {
for (ComponentLogEntry entry : componentLog.getComponentLogEntries()) {
componentsDetails.put(entry.getName(), Component.builder().name(entry.getName()).componentValues(toComponentList(entry)).build());
}
}
return FileComponents.builder()
.dossierTemplateId(dossierTemplateId)
.dossierId(dossierId)
.filename(fileStatusService.getFileName(fileId))
.fileId(fileId)
.components(basicComponent)
.componentDetails(componentsDetails)
.build();
}
private List<ComponentValue> toComponentList(ComponentLogEntry componentLogEntry) {
return componentLogEntry.getComponentValues()
.stream()
.map(this::convert)
.toList();
}
private ComponentValue convert(ComponentLogEntryValue componentValue) {
return ComponentValue.builder()
.valueDescription(componentValue.getValueDescription())
.componentRuleId(componentValue.getComponentRuleId())
.entityReferences(componentValue.getComponentLogEntityReferences()
.stream()
.map(this::convertComponentEntityReference)
.toList())
.originalValue(componentValue.getOriginalValue())
.value(componentValue.getValue())
.build();
}
private EntityReference convertComponentEntityReference(ComponentLogEntityReference componentLogEntityReference) {
return EntityReference.builder()
.id(componentLogEntityReference.getId())
.entityRuleId(componentLogEntityReference.getEntityRuleId())
.type(componentLogEntityReference.getType())
.page(componentLogEntityReference.getPage())
.build();
return componentMapper.toFileComponents(componentLog, dossierTemplateId, dossierId, fileId, fileStatusService.getFileName(fileId), includeDetails);
}
@ -127,4 +69,44 @@ public class ComponentControllerV2 implements ComponentResource {
}
@Override
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverride(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestBody Component override) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
componentLogService.addOverride(dossierId, fileId, componentMapper.toComponentLogEntry(override));
}
@Override
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public ComponentOverrideModelList getOverrides(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
var overrides = componentLogService.getOverrides(dossierId, fileId);
var componentOverrides = componentMapper.toComponents(overrides);
return ComponentOverrideModelList.builder().dossierTemplateId(dossierTemplateId).dossierId(dossierId).fileId(fileId).componentOverrideModels(componentOverrides).build();
}
@Override
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void revertOverrides(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestBody RevertOverrideRequest revertOverrideRequest) {
dossierTemplateController.getDossierTemplate(dossierTemplateId);
componentLogService.revertOverrides(dossierId, fileId, revertOverrideRequest);
}
}

View File

@ -0,0 +1,75 @@
package com.iqser.red.persistence.service.v2.external.api.impl.mapper;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v2.api.external.model.Component;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentValue;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponents;
@Mapper
public interface ComponentMapper {
ComponentMapper INSTANCE = Mappers.getMapper(ComponentMapper.class);
@Mapping(source = "componentLogEntityReferences", target = "entityReferences")
ComponentValue toComponentValue(ComponentLogEntryValue entry);
@Mapping(source = "entityReferences", target = "componentLogEntityReferences")
ComponentLogEntryValue toComponentLogEntry(ComponentValue value);
List<ComponentValue> toComponentValues(List<ComponentLogEntryValue> entries);
List<ComponentLogEntryValue> toComponentLogEntries(List<ComponentValue> values);
@Mapping(source = "componentValues", target = "componentValues")
Component toComponent(ComponentLogEntry entry);
List<Component> toComponents(List<ComponentLogEntry> entries);
@Mapping(source = "componentValues", target = "componentValues")
ComponentLogEntry toComponentLogEntry(Component component);
default FileComponents toFileComponents(ComponentLog componentLog, String dossierTemplateId, String dossierId, String fileId, String fileName, boolean includeDetails) {
Map<String, List<String>> basicComponent = new LinkedHashMap<>();
for (ComponentLogEntry componentLogEntry : componentLog.getComponentLogEntries()) {
basicComponent.put(componentLogEntry.getName(),
componentLogEntry.getComponentValues()
.stream()
.map(ComponentLogEntryValue::getValue)
.toList());
}
Map<String, Component> componentsDetails = new LinkedHashMap<>();
if (includeDetails) {
for (ComponentLogEntry entry : componentLog.getComponentLogEntries()) {
componentsDetails.put(entry.getName(), toComponent(entry));
}
}
return FileComponents.builder()
.dossierTemplateId(dossierTemplateId)
.dossierId(dossierId)
.filename(fileName)
.fileId(fileId)
.components(basicComponent)
.componentDetails(componentsDetails)
.build();
}
}

View File

@ -9,9 +9,10 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.view.RedirectView;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import io.swagger.v3.oas.annotations.Operation;
@ -25,11 +26,13 @@ public interface ComponentLogResource {
String OVERRIDE_PATH = "/override";
String DOSSIER_TEMPLATE_ID = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID + "}";
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
@ -46,7 +49,7 @@ public interface ComponentLogResource {
@PostMapping(value = COMPONENT_LOG_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds overrides for components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides);
RedirectView addOverride(String dossierTemplateId, @PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentLogEntry override);
@ResponseBody
@ -54,7 +57,7 @@ public interface ComponentLogResource {
@GetMapping(value = COMPONENT_LOG_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets overrides for components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
RedirectView getOverrides(String dossierTemplateId, @PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseBody
@ -62,6 +65,6 @@ public interface ComponentLogResource {
@PostMapping(value = COMPONENT_LOG_PATH + OVERRIDE_PATH + "/revert" + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Reverts overrides for components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest);
RedirectView revertOverrides(String dossierTemplateId, @PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest);
}

View File

@ -1,76 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@Deprecated(forRemoval = true)
@ResponseStatus(value = HttpStatus.OK)
public interface RSSResource {
String RSS_PATH = ExternalApi.BASE_PATH + "/rss";
String OVERRIDE_PATH = "/override";
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID + "}";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
@Deprecated(forRemoval = true)
@GetMapping(value = RSS_PATH + DOSSIER_ID_PATH_VARIABLE, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@Operation(summary = "Returns the RSS response for a dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
RSSResponse getRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId);
@Deprecated(forRemoval = true)
@GetMapping(value = RSS_PATH + "/detailed" + DOSSIER_ID_PATH_VARIABLE, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@Operation(summary = "Returns the RSS response with more details for a dossier", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
DetailedRSSResponse getDetailedRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId);
@Deprecated(forRemoval = true)
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = RSS_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds overrides for RSS components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides);
@Deprecated(forRemoval = true)
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = RSS_PATH + OVERRIDE_PATH + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Gets overrides for RSS components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
@Deprecated(forRemoval = true)
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = RSS_PATH + OVERRIDE_PATH + "/revert" + DOSSIER_ID_PATH_VARIABLE + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Reverts overrides for RSS components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest);
}

View File

@ -19,5 +19,7 @@ public class Component {
private String name;
@JacksonXmlCData
private List<ComponentValue> componentValues;
@JacksonXmlCData
private boolean overridden;
}

View File

@ -0,0 +1,22 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ComponentOverrideModelList {
String dossierTemplateId;
String dossierId;
String fileId;
List<Component> componentOverrideModels = new ArrayList<>();
}

View File

@ -18,6 +18,7 @@ public class ComponentValue {
@JacksonXmlCData
String value;
@JacksonXmlCData
@Deprecated
String originalValue;
@JacksonXmlCData
String valueDescription;

View File

@ -11,26 +11,22 @@ import static com.iqser.red.service.persistence.service.v2.api.external.resource
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingMetadataModel;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentMappingSummary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.Component;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentOverrideModelList;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponents;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponentsList;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -49,10 +45,15 @@ public interface ComponentResource {
String INCLUDE_DETAILS_DESCRIPTION = """
A toggle to decide whether to include detailed component information in the response:
- true: The component object's field componentDetails stores detailed information about the source of its respective value(s).
- false (default): The component object does not contain a field componentDetails.
""";
String OVERRIDES_PATH = "/overrides";
String REVERT_PATH = "/revert";
String COMPONENT_OVERRIDE_PARAM = "componentOverride";
@GetMapping(value = FILE_PATH + FILE_ID_PATH_VARIABLE + COMPONENTS_PATH, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@ -72,5 +73,35 @@ public interface ComponentResource {
@Parameter(name = INCLUDE_DETAILS_PARAM, description = INCLUDE_DETAILS_DESCRIPTION) @RequestParam(name = INCLUDE_DETAILS_PARAM, defaultValue = "false", required = false) boolean includeDetails);
@ResponseBody
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = PATH + FILE_ID_PATH_VARIABLE + OVERRIDES_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Adds overrides for components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void addOverride(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Parameter(name = DOSSIER_ID_PARAM, description = "The identifier of the dossier that contains the file.", required = true) @PathVariable(DOSSIER_ID_PARAM) String dossierId,
@Parameter(name = FILE_ID_PARAM, description = "The identifier of the file that the components are requested for.", required = true) @PathVariable(FILE_ID_PARAM) String fileId,
@Parameter(name = COMPONENT_OVERRIDE_PARAM, description = "The object to override the component.", required = true) @RequestBody Component componentOverrideModel);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = PATH + FILE_ID_PATH_VARIABLE + OVERRIDES_PATH, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
@Operation(summary = "Gets overrides for components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
ComponentOverrideModelList getOverrides(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Parameter(name = DOSSIER_ID_PARAM, description = "The identifier of the dossier that contains the file.", required = true) @PathVariable(DOSSIER_ID_PARAM) String dossierId,
@Parameter(name = FILE_ID_PARAM, description = "The identifier of the file that the components are requested for.", required = true) @PathVariable(FILE_ID_PARAM) String fileId);
@ResponseBody
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = PATH + FILE_ID_PATH_VARIABLE + OVERRIDES_PATH + REVERT_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Reverts overrides for components", description = "None")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "403", description = "Forbidden")})
void revertOverrides(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Parameter(name = DOSSIER_ID_PARAM, description = "The identifier of the dossier that contains the file.", required = true) @PathVariable(DOSSIER_ID_PARAM) String dossierId,
@Parameter(name = FILE_ID_PARAM, description = "The identifier of the file that the components are requested for.", required = true) @PathVariable(FILE_ID_PARAM) String fileId,
@RequestBody RevertOverrideRequest revertOverrideRequest);
}

View File

@ -0,0 +1,21 @@
package com.iqser.red.service.persistence.management.v1.processor.model;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ComponentOverride {
String name;
List<ComponentLogEntryValue> componentOverrideValues = new ArrayList<>();
}

View File

@ -2,13 +2,19 @@ package com.iqser.red.service.persistence.management.v1.processor.service;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Map;
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.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.ComponentLogMongoService;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@ -19,7 +25,8 @@ import lombok.experimental.FieldDefaults;
public class ComponentLogService {
private final FileManagementStorageService fileManagementStorageService;
private final ComponentOverrideService componentOverrideService;
private final ComponentLogMongoService componentLogMongoService;
private final AuditPersistenceService auditPersistenceService;
// TODO: make this DB changeable!
private static final List<String> ORDER = List.of("Study_Title",
@ -65,30 +72,43 @@ public class ComponentLogService {
public ComponentLog getComponentLog(String dossierId, String fileId, boolean includeOverrides) {
ComponentLog componentLog = sortComponentLogEntriesByOrderList(fileManagementStorageService.getComponentLog(dossierId, fileId), ORDER);
ComponentLog componentLog;
try {
componentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);
} catch (NotFoundException e) {
var componentLogOptional = componentLogMongoService.findComponentLogByDossierIdAndFileId(dossierId, fileId);
if (componentLogOptional.isEmpty()) {
throw new NotFoundException(e.getMessage());
}
componentLog = componentLogOptional.get();
}
if (!includeOverrides) {
componentLog = sortComponentLogEntriesByOrderList(componentLog, ORDER);
return componentLog;
}
ComponentsOverrides componentsOverrides = componentOverrideService.getOverrides(dossierId, fileId);
if (Objects.isNull(componentsOverrides.getComponentOverrides()) || componentsOverrides.getComponentOverrides().isEmpty()) {
return componentLog;
}
List<ComponentLogEntry> componentOverrides = getOverrides(dossierId, fileId);
List<ComponentLogEntry> overriddenComponentLogEntries = componentLog.getComponentLogEntries()
.stream()
.map(componentLogEntry -> applyOverride(componentLogEntry,
componentsOverrides.getComponentOverrides()
.get(componentLogEntry.getName())))
.toList();
replaceOverriddenComponentLogEntries(componentLog, componentOverrides);
componentLog.setComponentLogEntries(overriddenComponentLogEntries);
componentLog = sortComponentLogEntriesByOrderList(componentLog, ORDER);
return componentLog;
}
private void replaceOverriddenComponentLogEntries(ComponentLog componentLog, List<ComponentLogEntry> componentOverrides) {
// remove override entries from componentLog
componentLog.getComponentLogEntries()
.removeIf(entry -> componentOverrides.stream()
.anyMatch(override -> entry.getName().equals(override.getName())));
// insert overrides to Component log
componentLog.getComponentLogEntries().addAll(componentOverrides);
}
private ComponentLog sortComponentLogEntriesByOrderList(ComponentLog componentLog, List<String> order) {
return new ComponentLog(componentLog.getAnalysisNumber(),
@ -107,14 +127,93 @@ public class ComponentLogService {
}
private ComponentLogEntry applyOverride(ComponentLogEntry componentLogEntry, String override) {
public void addOverride(String dossierId, String fileId, ComponentLogEntry componentOverride) {
if (Objects.isNull(override)) {
return componentLogEntry;
var optionalComponentLogEntry = componentLogMongoService.findComponentLogEntryById(dossierId, fileId, componentOverride.getName());
if (optionalComponentLogEntry.isPresent()) {
ComponentLogEntry componentToUpdate = optionalComponentLogEntry.get();
componentToUpdate.setComponentValues(componentOverride.getComponentValues());
saveOverride(dossierId, fileId, componentToUpdate);
auditOverride(dossierId, fileId, componentToUpdate);
} else {
insertOverride(dossierId, fileId, componentOverride);
auditOverride(dossierId, fileId, componentOverride);
}
componentLogEntry.getComponentValues()
.forEach(componentValue -> componentValue.setValue(override));
return componentLogEntry;
}
private void saveOverride(String dossierId, String fileId, ComponentLogEntry componentToUpdate) {
componentLogMongoService.saveComponentLogEntries(dossierId, fileId, List.of(componentToUpdate));
}
private void insertOverride(String dossierId, String fileId, ComponentLogEntry componentToAdd) {
componentLogMongoService.insertComponentLogEntries(dossierId, fileId, List.of(componentToAdd));
}
public List<ComponentLogEntry> getOverrides(String dossierId, String fileId) {
return componentLogMongoService.findOverrides(dossierId, fileId);
}
public void revertOverrides(String dossierId, String fileId, RevertOverrideRequest revertOverrideRequest) {
revertOverrideRequest.getComponents()
.forEach(componentName -> {
var componentLogEntry = componentLogMongoService.findComponentLogEntryById(dossierId, fileId, componentName)
.orElseThrow(() -> new NotFoundException(String.format("Component %s was not found.", componentName)));
auditOverrideRevert(dossierId, fileId, componentLogEntry);
});
}
private void auditOverride(String dossierId, String fileId, ComponentLogEntry entry) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component is overwritten with value")
.details(Map.of("dossierId",
dossierId,
"fileId",
fileId,
"ComponentName",
entry.getName(),
"Action",
"MODIFY",
"Values",
entry.getComponentValues()))
.build());
}
private void auditOverrideRevert(String dossierId, String fileId, ComponentLogEntry entry) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component override for was reverted")
.details(Map.of("dossierId",
dossierId,
"fileId",
fileId,
"ComponentName",
entry.getName(),
"Action",
"REVERT",
"Values",
entry.getComponentValues()))
.build());
}
@ -148,4 +247,4 @@ public class ComponentLogService {
}
}
}

View File

@ -1,68 +0,0 @@
package com.iqser.red.service.persistence.management.v1.processor.service;
import java.io.ByteArrayInputStream;
import java.util.Collections;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@Service
@RequiredArgsConstructor
public class ComponentOverrideService {
private final FileManagementStorageService fileManagementStorageService;
private final ObjectMapper objectMapper;
@SneakyThrows
public void addOverrides(String dossierId, String fileId, ComponentsOverrides componentsOverrides) {
if (!fileManagementStorageService.objectExists(dossierId, fileId, FileType.COMPONENTS)) {
fileManagementStorageService.storeObject(dossierId, fileId, FileType.COMPONENTS, new ByteArrayInputStream(objectMapper.writeValueAsBytes(componentsOverrides)));
return;
}
var existingComponentsBytes = fileManagementStorageService.getStoredObjectBytes(dossierId, fileId, FileType.COMPONENTS);
var existingComponents = objectMapper.readValue(existingComponentsBytes, ComponentsOverrides.class);
existingComponents.getComponentOverrides().putAll(componentsOverrides.getComponentOverrides());
fileManagementStorageService.storeObject(dossierId, fileId, FileType.COMPONENTS, new ByteArrayInputStream(objectMapper.writeValueAsBytes(existingComponents)));
}
@SneakyThrows
public ComponentsOverrides getOverrides(String dossierId, String fileId) {
var exists = fileManagementStorageService.objectExists(dossierId, fileId, FileType.COMPONENTS);
if (!exists) {
return ComponentsOverrides.builder().componentOverrides(Collections.emptyMap()).build();
}
var existingComponentsBytes = fileManagementStorageService.getStoredObjectBytes(dossierId, fileId, FileType.COMPONENTS);
return objectMapper.readValue(existingComponentsBytes, ComponentsOverrides.class);
}
@SneakyThrows
public void revertOverrides(String dossierId, String fileId, RevertOverrideRequest revertOverrideRequest) {
if (!fileManagementStorageService.objectExists(dossierId, fileId, FileType.COMPONENTS)) {
return;
}
var existingComponentsBytes = fileManagementStorageService.getStoredObjectBytes(dossierId, fileId, FileType.COMPONENTS);
var existingComponents = objectMapper.readValue(existingComponentsBytes, ComponentsOverrides.class);
revertOverrideRequest.getComponents()
.forEach(c -> existingComponents.getComponentOverrides().remove(c));
fileManagementStorageService.storeObject(dossierId, fileId, FileType.COMPONENTS, new ByteArrayInputStream(objectMapper.writeValueAsBytes(existingComponents)));
}
}

View File

@ -5,4 +5,5 @@
<include file="/mongo/changelog/tenant/1-initial-database.changelog.xml"/>
<include file="/mongo/changelog/tenant/2-create-indices-for-entries.xml"/>
<include file="/mongo/changelog/tenant/3-add-page-paragraph-idx.xml"/>
<include file="/mongo/changelog/tenant/4-create-component-entities.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,17 @@
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="createComponentCollection" author="ali">
<ext:createCollection collectionName="component-logs"/>
<ext:createCollection collectionName="components"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,62 @@
//package com.iqser.red.service.peristence.v1.server.integration.tests;
//
//import static com.mongodb.assertions.Assertions.assertNotNull;
//import static com.mongodb.assertions.Assertions.assertTrue;
//import static org.junit.jupiter.api.Assertions.assertEquals;
//
//import java.util.List;
//import java.util.Optional;
//
//import org.junit.jupiter.api.Test;
//import org.springframework.core.io.ClassPathResource;
//
//import com.fasterxml.jackson.databind.ObjectMapper;
//import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
//import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentDocument;
//import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.ComponentLogDocumentMapper;
//
//import lombok.SneakyThrows;
//
//public class ComponentLogDocumentMapperTest {
//
// private final ComponentLogDocumentMapper mapper = ComponentLogDocumentMapper.INSTANCE;
//
// private final String COMPONENT_LOG = "files/componentlog/componentLogExample.json";
// private final String COMPONENT_OVERRIDE = "files/componentlog/componentOverrideExample.json";
// private static final String TEST_DOSSIER_ID = "91ce8e90-9aec-473c-b8c3-cbe16443ad34";
// private static final String TEST_FILE_ID = "b2cbdd4dca0aa1aa0ebbfc5cc1462df0";
//
//
// @Test
// @SneakyThrows
// public void ComponentOverrideMapper() {
//
// var overrideFile = new ClassPathResource(String.format(COMPONENT_OVERRIDE));
// ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.registerModule(new JavaTimeModule());
//
// ComponentsOverrides componentsOverridesBefore = objectMapper.readValue(overrideFile.getInputStream(), ComponentsOverrides.class);
//
// List<ComponentDocument> componentDocument = mapper.toComponentOverrideDocuments(componentsOverridesBefore, TEST_DOSSIER_ID, TEST_FILE_ID);
//
// assertEquals(componentDocument.get(0).getDossierId(), TEST_DOSSIER_ID);
// assertEquals(componentDocument.get(0).getFileId(), TEST_FILE_ID);
// assertEquals(componentDocument.get(0).getId(), mapper.getComponentId(TEST_DOSSIER_ID, TEST_FILE_ID, componentDocument.get(0).getName()));
//
// Optional<ComponentValueDocument> optionalComponentValueDocument = componentDocument.get(0).getOverrideValues()
// .stream()
// .findFirst();
//
// assertTrue(optionalComponentValueDocument.isPresent());
// assertNotNull(optionalComponentValueDocument.get().getValueId());
// assertNotNull(optionalComponentValueDocument.get().getComponentOverrideId());
//
// ComponentsOverrides componentsOverridesAfter = mapper.fromComponentOverrideDocument(componentDocument.get(0));
//
// assertEquals(mapper.buildComponentOverrideMap(componentDocument.get(0).getName(),
// componentsOverridesBefore.getComponentOverrides()
// .get(componentDocument.get(0).getName())), componentsOverridesAfter);
//
// }
//
//}

View File

@ -0,0 +1,266 @@
{
"analysisNumber": 3,
"componentRulesVersion": 1,
"componentLogEntries": [
{
"name": "Study_Title",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "StudyTitle.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Performing_Laboratory",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "fallback",
"componentRuleId": "PerformingLaboratory.0.2",
"componentLogEntityReferences": []
}
]
},
{
"name": "Report_Number",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "ReportNumber.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "GLP_Study",
"componentValues": [
{
"valueId": null,
"value": "No",
"originalValue": "No",
"valueDescription": "Yes if present, No if not",
"componentRuleId": "GLPStudy.1.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Test_Guidelines_2",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all values of type with ', '",
"componentRuleId": "TestGuideline.2.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Experimental_Starting_Date",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Convert values of type '' to dd/MM/yyyy joined with ', '",
"componentRuleId": "StartDate.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Experimental_Completion_Date",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Convert values of type '' to dd/MM/yyyy joined with ', '",
"componentRuleId": "CompletionDate.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Certificate_of_Analysis_Batch_Identification",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "AnalysisCertificate.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Species",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "Species.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Strain",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "Strain.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Doses_mg_per_kg_bw",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "Necropsy.1.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Mortality_Statement",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "MortalityStatement.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Weight_Behavior_Changes",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all values of type with '\n'",
"componentRuleId": "WeightBehavior.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Necropsy_Findings",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "Necropsy.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Deviation_from_the_Guideline",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all values of type with '\n'",
"componentRuleId": "GuidelineDeviation.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_LD50_Greater_than",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "No entity of type 'ld50_greater' found",
"componentRuleId": "Conclusion.1.1",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_LD50_mg_per_kg",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "Conclusion.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_Minimum_Confidence",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "Conclusion.2.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_Maximum_Confidence",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "Conclusion.3.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Study_Conclusion",
"componentValues": [
{
"valueId": null,
"value": "",
"originalValue": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "StudyConclusion.0.0",
"componentLogEntityReferences": []
}
]
}
]
}

View File

@ -0,0 +1,244 @@
{
"componentOverrides": [
{
"name": "Study_Title",
"componentValues": [
{
"valueId": "1",
"value": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "StudyTitle.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Performing_Laboratory",
"componentValues": [
{
"valueId": "2",
"value": "",
"valueDescription": "fallback",
"componentRuleId": "PerformingLaboratory.0.2",
"componentLogEntityReferences": []
}
]
},
{
"name": "Report_Number",
"componentValues": [
{
"valueId": "3",
"value": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "ReportNumber.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "GLP_Study",
"componentValues": [
{
"valueId": "4",
"value": "No",
"valueDescription": "Yes if present, No if not",
"componentRuleId": "GLPStudy.1.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Test_Guidelines_2",
"componentValues": [
{
"valueId": "5",
"value": "",
"valueDescription": "Joining all values of type with ', '",
"componentRuleId": "TestGuideline.2.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Experimental_Starting_Date",
"componentValues": [
{
"valueId": "6",
"value": "",
"valueDescription": "Convert values of type '' to dd/MM/yyyy joined with ', '",
"componentRuleId": "StartDate.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Experimental_Completion_Date",
"componentValues": [
{
"valueId": "7",
"value": "",
"valueDescription": "Convert values of type '' to dd/MM/yyyy joined with ', '",
"componentRuleId": "CompletionDate.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Certificate_of_Analysis_Batch_Identification",
"componentValues": [
{
"valueId": "8",
"value": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "AnalysisCertificate.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Species",
"componentValues": [
{
"valueId": "9",
"value": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "Species.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Strain",
"componentValues": [
{
"valueId": "10",
"value": "",
"valueDescription": "First found value of type or else ''",
"componentRuleId": "Strain.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Doses_mg_per_kg_bw",
"componentValues": [
{
"valueId": "11",
"value": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "Necropsy.1.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Mortality_Statement",
"componentValues": [
{
"valueId": "12",
"value": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "MortalityStatement.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Weight_Behavior_Changes",
"componentValues": [
{
"valueId": "13",
"value": "",
"valueDescription": "Joining all values of type with '\n'",
"componentRuleId": "WeightBehavior.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Necropsy_Findings",
"componentValues": [
{
"valueId": "14",
"value": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "Necropsy.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Deviation_from_the_Guideline",
"componentValues": [
{
"valueId": "15",
"value": "",
"valueDescription": "Joining all values of type with '\n'",
"componentRuleId": "GuidelineDeviation.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_LD50_Greater_than",
"componentValues": [
{
"valueId": "16",
"value": "",
"valueDescription": "No entity of type 'ld50_greater' found",
"componentRuleId": "Conclusion.1.1",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_LD50_mg_per_kg",
"componentValues": [
{
"valueId": "17",
"value": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "Conclusion.0.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_Minimum_Confidence",
"componentValues": [
{
"valueId": "18",
"value": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "Conclusion.2.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Conclusion_Maximum_Confidence",
"componentValues": [
{
"valueId": "19",
"value": "",
"valueDescription": "Joining all unique values of type with ', '",
"componentRuleId": "Conclusion.3.0",
"componentLogEntityReferences": []
}
]
},
{
"name": "Study_Conclusion",
"componentValues": [
{
"valueId": "20",
"value": "",
"valueDescription": "Joining all values of type with ' '",
"componentRuleId": "StudyConclusion.0.0",
"componentLogEntityReferences": []
}
]
}
]
}

View File

@ -16,5 +16,5 @@ public class ComponentLogEntry {
String name;
List<ComponentLogEntryValue> componentValues;
boolean overridden;
}

View File

@ -1,19 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.component;
import java.util.HashMap;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ComponentsOverrides {
private Map<String, String> componentOverrides = new HashMap<>();
}

View File

@ -0,0 +1,39 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.document;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
@Document(collection = "components")
public class ComponentDocument {
@Id
@EqualsAndHashCode.Include
String id; // componentLogId/name = componentOverrideId
String componentLogId;
String name;
List<ComponentLogEntryValue> overrideValues = new ArrayList<>();
// these parameters will be needed later
// List<ComponentLogEntryValue> values = new ArrayList<>();
// boolean overridden;
}

View File

@ -0,0 +1,38 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.document;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
@Document(collection = "component-logs")
public class ComponentLogDocument {
@Id
@EqualsAndHashCode.Include
String id; // dossierId/fileId = componentLogId
String dossierId;
String fileId;
int analysisNumber;
long componentRulesVersion = -1;
@DBRef
List<ComponentDocument> components = new ArrayList<>();
}

View File

@ -0,0 +1,10 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.exception;
public class ComponentLogDocumentNotFoundException extends RuntimeException {
public ComponentLogDocumentNotFoundException(String errorMessage) {
super(errorMessage);
}
}

View File

@ -0,0 +1,56 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentLogDocument;
@Mapper
public interface ComponentLogDocumentMapper {
ComponentLogDocumentMapper INSTANCE = Mappers.getMapper(ComponentLogDocumentMapper.class);
@Mapping(source = "components", target = "componentLogEntries")
ComponentLog fromComponentLogDocument(ComponentLogDocument componentLogDocument);
List<ComponentDocument> toComponentDocuments(List<ComponentLogEntry> componentLogEntries);
@Mapping(source = "overrideValues", target = "componentValues")
ComponentLogEntry fromComponentDocument(ComponentDocument componentDocument);
@Mapping(expression = "java(getComponentLogId(dossierId, fileId))", target = "id")
ComponentLogDocument toComponentLogDocument(String dossierId, String fileId, ComponentLog componentLog);
@Mapping(expression = "java(getComponentId(componentLogId, componentLogEntry.getName()))", target = "id")
ComponentDocument toComponentDocument(String componentLogId, ComponentLogEntry componentLogEntry);
default String getComponentLogId(String dossierId, String fileId) {
return dossierId + "/" + fileId;
}
default String getComponentId(String componentLogId, String componentName) {
return componentLogId + "/" + componentName;
}
default String getComponentId(String dossierId, String fileId, String componentName) {
return getComponentLogId(dossierId, fileId) + "/" + componentName;
}
}

View File

@ -0,0 +1,22 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentDocument;
@Repository
public interface ComponentDocumentRepository extends MongoRepository<ComponentDocument, String> {
@Query(value = "{ 'componentLogId' : ?0}", delete = true)
void deleteByComponentLogId(String componentLogId);
@Query(value = "{ 'componentLogId': ?0, 'componentName': ?1 }")
Optional<ComponentDocument> findComponentDocumentByName(String componentLogId, String componentName);
}

View File

@ -0,0 +1,22 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import java.util.Optional;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentLogDocument;
@Repository
public interface ComponentLogDocumentRepository extends MongoRepository<ComponentLogDocument, String>, CustomComponentRepository {
@Query(value = "{ 'id' : ?0 }", fields = "{ 'analysisNumber' : 1 }")
Optional<ComponentLogDocument> findAnalysisNumberById(String id);
@Query(value = "{ 'id': ?0 }", fields = "{ 'components': 0 }")
Optional<ComponentLogDocument> findComponentLogDocumentWithoutEntriesById(String id);
}

View File

@ -0,0 +1,14 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentDocument;
@Repository
public interface CustomComponentRepository {
List<ComponentDocument> findOverrides(String fileId, String dossierId);
}

View File

@ -0,0 +1,49 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.replaceRoot;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind;
import java.util.List;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.LookupOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Repository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentDocument;
import lombok.RequiredArgsConstructor;
@Repository
@RequiredArgsConstructor
public class CustomComponentRepositoryImpl implements CustomComponentRepository {
private final MongoTemplate mongoTemplate;
@Override
public List<ComponentDocument> findOverrides(String fileId, String dossierId) {
LookupOperation lookupOperation = LookupOperation.newLookup().from("componentDocument").localField("components").foreignField("_id").as("componentDocs");
AggregationOperation matchOperation = match(Criteria.where("fileId").is(fileId).and("dossierId").is(dossierId));
AggregationOperation unwindOperation = unwind("componentDocs");
AggregationOperation matchNonEmptyOverrides = match(Criteria.where("componentDocs.overrideValues").ne(null));
AggregationOperation replaceRootOperation = replaceRoot("componentDocs");
Aggregation aggregation = newAggregation(matchOperation, lookupOperation, unwindOperation, matchNonEmptyOverrides, replaceRootOperation);
AggregationResults<ComponentDocument> results = mongoTemplate.aggregate(aggregation, "componentLogDocument", ComponentDocument.class);
return results.getMappedResults();
}
}

View File

@ -0,0 +1,229 @@
package com.iqser.red.service.persistence.service.v1.api.shared.mongo.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.ComponentLogDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.exception.ComponentLogDocumentNotFoundException;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.ComponentLogDocumentMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.ComponentDocumentRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.ComponentLogDocumentRepository;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
@Service
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class ComponentLogMongoService {
ComponentLogDocumentRepository componentLogDocumentRepository;
ComponentDocumentRepository componentDocumentRepository;
ComponentLogDocumentMapper mapper = ComponentLogDocumentMapper.INSTANCE;
public ComponentLogMongoService(ComponentLogDocumentRepository componentLogDocumentRepository, ComponentDocumentRepository componentDocumentRepository) {
this.componentLogDocumentRepository = componentLogDocumentRepository;
this.componentDocumentRepository = componentDocumentRepository;
}
public void insertComponentLog(String dossierId, String fileId, ComponentLog componentLog) {
ComponentLogDocument componentLogDocument = componentLogDocumentRepository.insert(mapper.toComponentLogDocument(dossierId, fileId, componentLog));
componentDocumentRepository.insert(componentLog.getComponentLogEntries()
.stream()
.map(componentLogEntry -> mapper.toComponentDocument(componentLogDocument.getId(), componentLogEntry))
.toList());
}
public void saveComponentLog(String dossierId, String fileId, ComponentLog componentLog) {
ComponentLogDocument componentLogDocument = componentLogDocumentRepository.save(mapper.toComponentLogDocument(dossierId, fileId, componentLog));
componentDocumentRepository.saveAll(componentLog.getComponentLogEntries()
.stream()
.map(componentLogEntry -> mapper.toComponentDocument(componentLogDocument.getId(), componentLogEntry))
.toList());
}
public void upsertComponentLog(String dossierId, String fileId, ComponentLog componentLog) {
Optional<ComponentLogDocument> optionalComponentLogDocument = componentLogDocumentRepository.findById(mapper.getComponentLogId(dossierId, fileId));
if (optionalComponentLogDocument.isEmpty()) {
insertComponentLog(dossierId, fileId, componentLog);
return;
}
ComponentLogDocument oldComponentLogDocument = optionalComponentLogDocument.get();
List<ComponentDocument> oldComponentDocuments = oldComponentLogDocument.getComponents();
ComponentLogDocument newComponentLogDocument = mapper.toComponentLogDocument(dossierId, fileId, componentLog);
List<ComponentDocument> newComponentDocuments = newComponentLogDocument.getComponents();
List<ComponentDocument> toUpdate = new ArrayList<>(newComponentDocuments);
toUpdate.retainAll(oldComponentDocuments);
List<ComponentDocument> toRemove = new ArrayList<>(oldComponentDocuments);
toRemove.removeAll(toUpdate);
List<ComponentDocument> toInsert = new ArrayList<>(newComponentDocuments);
toInsert.removeAll(toUpdate);
componentDocumentRepository.saveAll(toUpdate);
componentDocumentRepository.deleteAll(toRemove);
componentDocumentRepository.insert(toInsert);
componentLogDocumentRepository.save(newComponentLogDocument);
}
public void deleteComponentLog(String dossierId, String fileId) {
String componentLogId = mapper.getComponentLogId(dossierId, fileId);
componentLogDocumentRepository.deleteById(componentLogId);
componentDocumentRepository.deleteByComponentLogId(componentLogId);
}
public void insertComponentLogEntries(String dossierId, String fileId, List<ComponentLogEntry> componentLogEntries) {
String componentLogId = mapper.getComponentLogId(dossierId, fileId);
ComponentLogDocument componentLogDocument = getComponentLogDocument(componentLogId);
List<ComponentDocument> componentDocuments = componentLogEntries.stream()
.map(componentLogEntry -> mapper.toComponentDocument(componentLogId, componentLogEntry))
.toList();
componentLogDocument.getComponents().addAll(componentDocuments);
componentDocumentRepository.insert(componentDocuments);
componentLogDocumentRepository.save(componentLogDocument);
}
private ComponentLogDocument getComponentLogDocument(String componentLogId) {
Optional<ComponentLogDocument> optionalComponentLogDocument = componentLogDocumentRepository.findById(componentLogId);
if (optionalComponentLogDocument.isEmpty()) {
throw new ComponentLogDocumentNotFoundException(String.format("Component log not found for %s", componentLogId));
}
return optionalComponentLogDocument.get();
}
public void saveComponentLogEntries(String dossierId, String fileId, List<ComponentLogEntry> componentLogEntries) {
String componentLogId = mapper.getComponentLogId(dossierId, fileId);
ComponentLogDocument componentLogDocument = getComponentLogDocument(componentLogId);
List<ComponentDocument> componentDocuments = componentLogEntries.stream()
.map(componentLogEntry -> mapper.toComponentDocument(componentLogId, componentLogEntry))
.toList();
componentLogDocument.getComponents().addAll(componentDocuments);
componentDocumentRepository.saveAll(componentDocuments);
componentLogDocumentRepository.save(componentLogDocument);
}
public void updateComponentLogEntries(String dossierId, String fileId, List<ComponentLogEntry> componentLogEntries) {
String componentLogId = mapper.getComponentLogId(dossierId, fileId);
componentDocumentRepository.saveAll(componentLogEntries.stream()
.map(componentLogEntry -> mapper.toComponentDocument(componentLogId, componentLogEntry))
.toList());
}
public void deleteComponentLogEntries(String dossierId, String fileId, List<ComponentLogEntry> componentLogEntries) {
String componentLogId = mapper.getComponentLogId(dossierId, fileId);
ComponentLogDocument componentLogDocument = getComponentLogDocument(componentLogId);
List<ComponentDocument> componentDocuments = componentLogEntries.stream()
.map(componentLogEntry -> mapper.toComponentDocument(componentLogId, componentLogEntry))
.toList();
componentLogDocument.getComponents().removeAll(componentDocuments);
componentDocumentRepository.deleteAll(componentDocuments);
componentLogDocumentRepository.save(componentLogDocument);
}
public Optional<ComponentLog> findComponentLogByDossierIdAndFileId(String dossierId, String fileId) {
return componentLogDocumentRepository.findById(mapper.getComponentLogId(dossierId, fileId))
.map(mapper::fromComponentLogDocument);
}
public Optional<ComponentLogEntry> findComponentLogEntryById(String dossierId, String fileId, String componentName) {
return componentDocumentRepository.findComponentDocumentByName(mapper.getComponentLogId(dossierId, fileId), componentName)
.map(mapper::fromComponentDocument);
}
public List<ComponentLogEntry> findOverrides(String dossierId, String fileId) {
return componentLogDocumentRepository.findOverrides(dossierId, fileId)
.stream()
.map(mapper::fromComponentDocument)
.toList();
}
public boolean componentLogDocumentExists(String dossierId, String fileId) {
return componentLogDocumentRepository.existsById(mapper.getComponentLogId(dossierId, fileId));
}
public Optional<Integer> findLatestAnalysisNumber(String dossierId, String fileId) {
return componentLogDocumentRepository.findAnalysisNumberById(mapper.getComponentLogId(dossierId, fileId))
.map(ComponentLogDocument::getAnalysisNumber);
}
public List<ComponentLogEntry> findComponentLogEntriesWithComponentNameIn(String dossierId, String fileId, Collection<String> componentNames) {
String componentLogId = mapper.getComponentLogId(dossierId, fileId);
List<String> names = componentNames.stream()
.map(name -> mapper.getComponentId(componentLogId, name))
.toList();
return componentDocumentRepository.findAllById(names)
.stream()
.map(mapper::fromComponentDocument)
.toList();
}
public Optional<ComponentLog> findComponentLogWithoutEntries(String dossierId, String fileId) {
return componentLogDocumentRepository.findComponentLogDocumentWithoutEntriesById(mapper.getComponentLogId(dossierId, fileId))
.map(mapper::fromComponentLogDocument);
}
}