DM-410: Added endpoint to set that a timeout was detected during rule execution #109

Merged
dominique.eiflaender1 merged 1 commits from DM-410 into master 2023-09-07 11:17:12 +02:00
15 changed files with 159 additions and 97 deletions

View File

@ -1,13 +1,19 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_RULES;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.FileUploadException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RulesResource;
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.dossiertemplate.rules.RuleSyntaxErrorMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.Rules;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -20,20 +26,13 @@ import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.FileUploadException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RulesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RuleSyntaxErrorMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.Rules;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_RULES;
@Slf4j
@RestController
@ -49,7 +48,7 @@ public class RulesController implements RulesResource {
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<?> upload(@RequestBody Rules rules) {
public ResponseEntity<?> upload(@RequestBody RulesUploadRequest rules) {
DroolsSyntaxValidation droolsSyntaxValidation = redactionServiceClient.testRules(rules.getRules());
if (!droolsSyntaxValidation.isCompiled()) {
@ -76,7 +75,8 @@ public class RulesController implements RulesResource {
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public Rules download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
return new Rules(rulesPersistenceService.getRules(dossierTemplateId).getValue(), dossierTemplateId);
var ruleEntity = rulesPersistenceService.getRules(dossierTemplateId);
return new Rules(ruleEntity.getValue(), dossierTemplateId, ruleEntity.isTimeoutDetected());
}
@ -86,7 +86,7 @@ public class RulesController implements RulesResource {
public ResponseEntity<?> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestPart(name = "file") MultipartFile file) {
try {
return upload(new Rules(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId));
return upload(new RulesUploadRequest(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId));
} catch (IOException e) {
throw new FileUploadException("Could not upload file.", e);
}

View File

@ -1,22 +1,15 @@
package com.iqser.red.service.persistence.service.v1.api.external.resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.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.v1.api.shared.model.dossiertemplate.rules.Rules;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
public interface RulesResource {
@ -37,7 +30,7 @@ public interface RulesResource {
@PostMapping(value = RULES_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Takes object containing string or rules as argument, which will be used by the redaction service.")
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
ResponseEntity<?> upload(@RequestBody Rules rules);
ResponseEntity<?> upload(@RequestBody RulesUploadRequest rules);
@ResponseBody

View File

@ -1,9 +1,8 @@
package com.iqser.red.service.persistence.v1.internal.api.controller;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.util.stream.Collectors;
import com.iqser.red.commons.spring.ErrorMessage;
import com.iqser.red.service.persistence.management.v1.processor.exception.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
@ -12,14 +11,9 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.iqser.red.commons.spring.ErrorMessage;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.exception.InvalidRulesException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import lombok.extern.slf4j.Slf4j;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
@ -112,4 +106,12 @@ public class InternalControllerAdvice {
return new ErrorMessage(OffsetDateTime.now(), String.format("You have empty/wrong formatted parameters: %s", errorListAsString));
}
@ResponseBody
@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
@ExceptionHandler(value = RulesTimeoutDetectedException.class)
public ErrorMessage handleRulesTimeoutDetectedException(RulesTimeoutDetectedException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
}

View File

@ -1,14 +1,13 @@
package com.iqser.red.service.persistence.v1.internal.api.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.exception.RulesTimeoutDetectedException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.internal.resources.RulesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@ -28,10 +27,23 @@ public class RulesInternalController implements RulesResource {
public long getVersion(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
try {
return rulesPersistenceService.getRules(dossierTemplateId).getVersion();
var rules = rulesPersistenceService.getRules(dossierTemplateId);
if (rules.isTimeoutDetected()) {
throw new RulesTimeoutDetectedException(dossierTemplateId);
}
return rules.getVersion();
} catch (NotFoundException e) {
return 0;
}
}
@Override
public void setRulesTimeoutDetected(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
rulesPersistenceService.setTimeoutDetected(dossierTemplateId);
}
}

View File

@ -1,13 +1,9 @@
package com.iqser.red.service.persistence.service.v1.api.internal.resources;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import org.springframework.web.bind.annotation.*;
public interface RulesResource {
@ -17,6 +13,8 @@ public interface RulesResource {
String DOSSIER_TEMPLATE_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_PARAMETER_NAME + "}";
String VERSION_PATH = "/version";
String TIMEOUT_PATH = "/timeout";
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@ -28,4 +26,7 @@ public interface RulesResource {
@GetMapping(value = InternalApi.BASE_PATH + PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + VERSION_PATH)
long getVersion(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
@PostMapping(value = InternalApi.BASE_PATH + PATH + DOSSIER_TEMPLATE_PATH_VARIABLE + TIMEOUT_PATH)
void setRulesTimeoutDetected(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
}

View File

@ -1,11 +1,6 @@
package com.iqser.red.service.persistence.management.v1.processor.entity.configuration;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -28,4 +23,7 @@ public class RuleSetEntity {
@Column(name = "value", columnDefinition = "TEXT")
private String value;
@Column
private boolean timeoutDetected;
}

View File

@ -0,0 +1,12 @@
package com.iqser.red.service.persistence.management.v1.processor.exception;
public class RulesTimeoutDetectedException extends RuntimeException {
private static final String RULE_TIMEOUT_DETECTED_MESSAGE = "A timout (possible endless loop) was detected using the rules for dossierTemplateId %s";
public RulesTimeoutDetectedException(String dossierTemplateId) {
super(String.format(RULE_TIMEOUT_DETECTED_MESSAGE, dossierTemplateId));
}
}

View File

@ -1,13 +1,10 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.RuleSetEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.RuleSetRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
@ -38,15 +35,23 @@ public class RulesPersistenceService {
ruleSetRepository.findById(dossierTemplateId).ifPresentOrElse(r -> {
r.setValue(rules);
r.setVersion(r.getVersion() + 1);
r.setTimeoutDetected(false);
}, () -> {
RuleSetEntity ruleSet = new RuleSetEntity();
ruleSet.setDossierTemplateId(dossierTemplateId);
ruleSet.setValue(rules);
ruleSet.setVersion(1);
ruleSet.setTimeoutDetected(false);
ruleSetRepository.save(ruleSet);
});
}
@Transactional
public void setTimeoutDetected(String dossierTemplateId) {
ruleSetRepository.updateTimeoutDetected(dossierTemplateId);
}
}

View File

@ -1,9 +1,14 @@
package com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.RuleSetEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
public interface RuleSetRepository extends JpaRepository<RuleSetEntity, String> {
@Modifying
@Query("update RuleSetEntity r set r.timeoutDetected = true where r.dossierTemplateId = :dossierTemplatedId")
void updateTimeoutDetected(String dossierTemplatedId);
}

View File

@ -157,3 +157,5 @@ databaseChangeLog:
file: db/changelog/tenant/107-add-last-layout-processed-column.yaml
- include:
file: db/changelog/tenant/108-added-dictionary-changes-to-manual-recategorization.yaml
- include:
file: db/changelog/tenant/109-add-rules-timeout-detected-column.yaml

View File

@ -0,0 +1,13 @@
databaseChangeLog:
- changeSet:
id: add-rules-timeout-detected-column
author: dom
changes:
- addColumn:
columns:
- column:
name: timeout_detected
type: BOOLEAN
defaultValue: false
tableName: rule_set

View File

@ -1,24 +1,23 @@
package com.iqser.red.service.peristence.v1.server.integration.service;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.collect.Sets;
import com.iqser.red.service.peristence.v1.server.integration.client.DictionaryClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.client.LegalBasisClient;
import com.iqser.red.service.peristence.v1.server.integration.client.RulesClient;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.Rules;
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.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.legalbasis.LegalBasis;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@Service
public class DossierTemplateTesterAndProvider {
@ -76,7 +75,7 @@ public class DossierTemplateTesterAndProvider {
assertThat(loadedTemplate).isEqualTo(result);
rulesClient.upload(new Rules("ABCD", loadedTemplate.getDossierTemplateId()));
rulesClient.upload(new RulesUploadRequest("ABCD", loadedTemplate.getDossierTemplateId()));
legalBasisClient.setLegalBasisMapping(List.of(new LegalBasis("name", "description", "reason")), loadedTemplate.getDossierTemplateId());
loadedTemplate = dossierTemplateClient.getDossierTemplate(result.getDossierTemplateId());

View File

@ -1,17 +1,16 @@
package com.iqser.red.service.peristence.v1.server.integration.tests;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.iqser.red.service.peristence.v1.server.integration.client.RulesClient;
import com.iqser.red.service.peristence.v1.server.integration.client.VersionClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.Rules;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class RulesTest extends AbstractPersistenceServerServiceTest {
@ -30,13 +29,13 @@ public class RulesTest extends AbstractPersistenceServerServiceTest {
var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
rulesClient.upload(new Rules("lorem ipsum", dossierTemplate.getId()));
rulesClient.upload(new RulesUploadRequest("lorem ipsum", dossierTemplate.getId()));
assertThat(versionClient.getVersions(List.of(dossierTemplate.getId()))
.get(dossierTemplate.getId())
.getRulesVersion()).isEqualTo(3); //1. beim Anlegen des DossierTemplates; 2. bei provideTestTemplate(), damit es ACTIVE ist
assertThat(rulesClient.download(dossierTemplate.getId()).getRules()).isEqualTo("lorem ipsum");
rulesClient.upload(new Rules("lorem ipsum dolor sit amet", dossierTemplate.getId()));
rulesClient.upload(new RulesUploadRequest("lorem ipsum dolor sit amet", dossierTemplate.getId()));
assertThat(versionClient.getVersions(List.of(dossierTemplate.getId())).get(dossierTemplate.getId()).getRulesVersion()).isEqualTo(4);
assertThat(rulesClient.download(dossierTemplate.getId()).getRules()).isEqualTo("lorem ipsum dolor sit amet");

View File

@ -1,7 +1,6 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -18,4 +17,7 @@ public class Rules {
@Schema(description = "The DossierTemplate Id for these rules")
private String dossierTemplateId;
@Schema(description = "Bad written rules can lead to timeouts or endless processing. This will be detected by the system and all analyse request for the rules will be rejected. This flag indicates that a timeout was detected and you need to fix the rules")
private boolean timeoutDetected;
}

View File

@ -0,0 +1,19 @@
package com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "Object containing a string of Drools rules.")
public class RulesUploadRequest {
@Schema(description = "The actual string of rules.")
private String rules;
@Schema(description = "The DossierTemplate Id for these rules")
private String dossierTemplateId;
}