RED-7962: Implemented public rules endpoints

This commit is contained in:
Dominique Eifländer 2023-11-23 12:15:17 +01:00
parent 2344af108c
commit 19605b4185
5 changed files with 280 additions and 4 deletions

View File

@ -1,30 +1,166 @@
package com.iqser.red.persistence.service.v2.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.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierTemplateController;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService;
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.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
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.RulesUploadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.RulesValidationMessage;
import com.iqser.red.service.persistence.service.v2.api.external.model.RulesValidationResponse;
import com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource;
import com.iqser.red.service.redaction.v1.model.DroolsSyntaxValidation;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import feign.FeignException;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import lombok.SneakyThrows;
@RestController
@RequiredArgsConstructor
@Tag(name = "1. Dossier templates endpoints", description = "Provides operations related to dossier templates")
public class DossierTemplateControllerV2 implements DossierTemplateResource {
private static final String RULES_DOWNLOAD_FILE_NAME = "rules.drl";
private final DossierTemplateController dossierTemplateController;
private final RulesPersistenceService rulesPersistenceService;
private final RulesValidationService rulesValidationService;
private final AuditPersistenceService auditPersistenceService;
public List<DossierTemplateModel> getAllDossierTemplates() {
return dossierTemplateController.getAllDossierTemplates();
}
public DossierTemplateModel getDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
return dossierTemplateController.getDossierTemplate(dossierTemplateId);
}
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<RulesValidationResponse> uploadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
return uploadRules(dossierTemplateId, RuleFileType.ENTITY, file, dryRun);
}
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<InputStreamResource> downloadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
return downloadRules(dossierTemplateId, RuleFileType.ENTITY);
}
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public ResponseEntity<RulesValidationResponse> uploadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false, defaultValue = "false") boolean dryRun) {
return uploadRules(dossierTemplateId, RuleFileType.COMPONENT, file, dryRun);
}
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<InputStreamResource> downloadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType) {
return downloadRules(dossierTemplateId, RuleFileType.COMPONENT);
}
@SneakyThrows
private ResponseEntity<RulesValidationResponse> uploadRules(String dossierTemplateId, RuleFileType ruleFileType, MultipartFile file, boolean dryRun) {
var rulesUploadRequest = RulesUploadRequest.builder()
.rules(new String(file.getBytes(), StandardCharsets.UTF_8))
.dossierTemplateId(dossierTemplateId)
.ruleFileType(ruleFileType)
.build();
try {
DroolsSyntaxValidation droolsSyntaxValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
if (!droolsSyntaxValidation.isCompiled()) {
var rulesSyntaxErrorMessages = droolsSyntaxValidation.getDroolsSyntaxErrorMessages()
.stream()
.map(errorMessage -> RulesValidationMessage.builder()
.line(errorMessage.getLine())
.column(errorMessage.getColumn())
.message(errorMessage.getMessage())
.build())
.toList();
// TODO Add warning and deprecations to response
return new ResponseEntity<>(RulesValidationResponse.builder().errors(rulesSyntaxErrorMessages).build(), HttpStatus.BAD_REQUEST);
}
} catch (FeignException e) {
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
throw new BadRequestException("The provided rule string is not a valid drools rule file!");
}
}
if (!dryRun) {
rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType());
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(rulesUploadRequest.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message(String.format("%s Rules have been updated", rulesUploadRequest.getRuleFileType()))
.build());
// TODO Add warning and deprecations to response
return new ResponseEntity<>(RulesValidationResponse.builder().build(), HttpStatus.OK);
}
private ResponseEntity<InputStreamResource> downloadRules(String dossierTemplateId, RuleFileType ruleFileType) {
var ruleEntity = rulesPersistenceService.getRules(dossierTemplateId, ruleFileType);
var data = ruleEntity.getValue().getBytes(StandardCharsets.UTF_8);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
httpHeaders.add("Content-Disposition", "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(ruleFileType.name() + "_" + RULES_DOWNLOAD_FILE_NAME));
InputStream is = new ByteArrayInputStream(data);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
}

View File

@ -0,0 +1,28 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Schema(description = "Show where deprecated methods are used in the rules file.")
public class RulesDeprecationMessage {
@Schema(description = "The Line where the deprecated method is used.")
Integer line;
@Schema(description = "The Column where the deprecated method is used.")
Integer column;
@Schema(description = "The deprecated message, should point to method that should be used instead.")
String message;
@Schema(description = "The name of the deprecated method.")
String methodName;
}

View File

@ -0,0 +1,26 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Schema(description = "Shows where to find problems in the uploaded rules file")
public class RulesValidationMessage {
@Schema(description = "The Line where it occurred.")
Integer line;
@Schema(description = "The Column where it occurred.")
Integer column;
@Schema(description = "The error or warning message.")
String message;
}

View File

@ -0,0 +1,34 @@
package com.iqser.red.service.persistence.service.v2.api.external.model;
import java.util.ArrayList;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@Schema(description = "Object containing rules validation result")
public class RulesValidationResponse {
@Builder.Default
@Schema(description = "Show where errors are in the uploaded rules file.")
List<RulesValidationMessage> errors = new ArrayList<>();
/*
@Builder.Default
@Schema(description = "Show where warnings are in the uploaded rules file.")
List<RulesValidationMessage> warnings = new ArrayList<>();
@Builder.Default
@Schema(description = "Show deprecated methods are used in the uploaded rules file.")
List<RulesDeprecationMessage> deprecationWarnings = new ArrayList<>();
*/
}

View File

@ -1,14 +1,27 @@
package com.iqser.red.service.persistence.service.v2.api.external.resource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v2.api.external.model.RulesValidationResponse;
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;
import org.springframework.core.io.InputStreamResource;
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.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 java.util.List;
@ -18,9 +31,16 @@ public interface DossierTemplateResource {
String DOSSIER_TEMPLATE_PATH = "/dossier-templates";
String PATH = ExternalApiConstants.BASE_PATH + DOSSIER_TEMPLATE_PATH;
String ENTITY_RULES_PATH = "/entity-rules";
String COMPONENT_RULES_PATH = "/component-rules";
String DOSSIER_TEMPLATE_ID_PARAM = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID_PARAM + "}";
String RULE_FILE_TYPE_PARAMETER_NAME = "ruleFileType";
String DRY_RUN_PARAM = "dry-run";
@GetMapping(value = PATH, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Lists all existing DossierTemplates.", description = "None")
@ -34,5 +54,37 @@ public interface DossierTemplateResource {
DossierTemplateModel getDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + ENTITY_RULES_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Upload a component or entity rules file in drools format for a specific DossierTemplate.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
ResponseEntity<RulesValidationResponse> uploadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false ,defaultValue = "false") boolean dryRun);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns file containing the currently used Drools rules.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + ENTITY_RULES_PATH, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
ResponseEntity<InputStreamResource> downloadEntityRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_RULES_PATH, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Upload a component or entity rules file in drools format for a specific DossierTemplate.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Rules upload successful."), @ApiResponse(responseCode = "400", description = "Uploaded rules could not be verified.")})
ResponseEntity<RulesValidationResponse> uploadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file,
@Parameter(name = DRY_RUN_PARAM, description = "If true rules will be only validated not stored.") @RequestParam(value = DRY_RUN_PARAM, required = false ,defaultValue = "false") boolean dryRun);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@Operation(summary = "Returns file containing the currently used Drools rules.")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
@GetMapping(value = PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + COMPONENT_RULES_PATH, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
ResponseEntity<InputStreamResource> downloadComponentRules(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType);
}