diff --git a/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java b/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java index 9793ff987..d8a0ff87c 100644 --- a/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java +++ b/persistence-service-v1/persistence-service-external-api-impl-v2/src/main/java/com/iqser/red/persistence/service/v2/external/api/impl/controller/DossierTemplateControllerV2.java @@ -6,6 +6,7 @@ import static com.iqser.red.service.persistence.management.v1.processor.roles.Ac import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_FILE_ATTRIBUTES_CONFIG; 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 static java.lang.String.format; import java.io.ByteArrayInputStream; import java.io.FileOutputStream; @@ -208,20 +209,28 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource { dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId); - String nameToUse = Strings.isNullOrEmpty(name) ? file.getOriginalFilename() : name; - - if (Strings.isNullOrEmpty(nameToUse)) { - throw new BadRequestException("The provided file name is not valid!"); + if (Strings.isNullOrEmpty(file.getOriginalFilename()) || !file.getOriginalFilename().endsWith(".csv")) { + throw new BadRequestException(format("File name \"%s\" does not end with .csv", file.getOriginalFilename())); } - if (Strings.isNullOrEmpty(delimiter) || delimiter.length() != 1) { - throw new BadRequestException("The provided delimiter is not valid! Only a single character is allowed."); + String fileName = file.getOriginalFilename(); + + String nameToUse = Strings.isNullOrEmpty(name) ? fileName.replaceAll(".csv$", "") : name; + + if (Strings.isNullOrEmpty(nameToUse)) { + throw new BadRequestException(format("The provided file name \"%s\" is not valid!", nameToUse)); + } + + if (Strings.isNullOrEmpty(delimiter)) { + throw new BadRequestException("The provided delimiter is not valid! Can't be null or empty."); + } else if (delimiter.length() != 1) { + throw new BadRequestException(format("The provided delimiter %s is not valid! Only a single character is allowed.", delimiter)); } char cleanDelimiter = delimiter.charAt(0); Path mappingFile = saveToFile(file); + try { - String fileName = file.getOriginalFilename() == null ? nameToUse + ".csv" : file.getOriginalFilename(); ComponentMappingMetadata metaData = componentMappingService.create(dossierTemplateId, nameToUse, fileName, cleanDelimiter, encoding, mappingFile.toFile()); @@ -236,14 +245,42 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource { @Override @SneakyThrows @PreAuthorize("hasAuthority('" + WRITE_RULES + "')") - public ComponentMappingMetadataModel updateMapping(String dossierTemplateId, String componentMappingId, MultipartFile file, String encoding, char delimiter) { + public ComponentMappingMetadataModel updateMapping(String dossierTemplateId, String componentMappingId, MultipartFile file, String name, String encoding, String delimiter) { dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId); + if (Strings.isNullOrEmpty(file.getOriginalFilename()) || !file.getOriginalFilename().endsWith(".csv")) { + throw new BadRequestException(format("File name \"%s\" does not end with .csv", file.getOriginalFilename())); + } + + String fileName = file.getOriginalFilename(); + + String nameToUse = Strings.isNullOrEmpty(name) ? fileName.replaceAll(".csv$", "") : name; + + if (Strings.isNullOrEmpty(nameToUse)) { + throw new BadRequestException(format("The provided file name \"%s\" is not valid!", nameToUse)); + } + + if (Strings.isNullOrEmpty(delimiter)) { + throw new BadRequestException("The provided delimiter is not valid! Can't be null or empty."); + } else if (delimiter.length() != 1) { + throw new BadRequestException(format("The provided delimiter %s is not valid! Only a single character is allowed.", delimiter)); + } + char cleanDelimiter = delimiter.charAt(0); + + if (Strings.isNullOrEmpty(delimiter) || delimiter.length() != 1) { + throw new BadRequestException("The provided delimiter is not valid! Only a single character is allowed."); + } + Path mappingFile = saveToFile(file); try { - ComponentMappingMetadata resultMetaData = componentMappingService.update(dossierTemplateId, componentMappingId, encoding, delimiter, mappingFile.toFile()); + ComponentMappingMetadata resultMetaData = componentMappingService.update(dossierTemplateId, + componentMappingId, + nameToUse, + encoding, + cleanDelimiter, + mappingFile.toFile()); return componentMappingMapper.toModel(resultMetaData); } finally { @@ -508,7 +545,7 @@ public class DossierTemplateControllerV2 implements DossierTemplateResource { .userId(KeycloakSecurity.getUserId()) .objectId(rulesUploadRequest.getDossierTemplateId()) .category(AuditCategory.DOSSIER_TEMPLATE.name()) - .message(String.format("%s rules have been %s", rulesUploadRequest.getRuleFileType(), dryRun ? "validated" : "updated")) + .message(format("%s rules have been %s", rulesUploadRequest.getRuleFileType(), dryRun ? "validated" : "updated")) .build()); return new ResponseEntity<>(rulesValidationResponse, HttpStatus.OK); diff --git a/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java b/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java index 0283e562a..a942b908d 100644 --- a/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java +++ b/persistence-service-v1/persistence-service-external-api-v2/src/main/java/com/iqser/red/service/persistence/service/v2/api/external/resource/DossierTemplateResource.java @@ -1,6 +1,7 @@ package com.iqser.red.service.persistence.service.v2.api.external.resource; import java.util.List; + import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel; import com.iqser.red.service.persistence.service.v2.api.external.model.DossierAttributeDefinitionList; import com.iqser.red.service.persistence.service.v2.api.external.model.DossierStatusDefinitionList; @@ -152,8 +153,9 @@ public interface DossierTemplateResource { ComponentMappingMetadataModel updateMapping(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId, @PathVariable(COMPONENT_MAPPING_ID_PARAM) String componentMappingId, @Schema(type = "string", format = "binary", name = "file") @RequestPart(name = "file") MultipartFile file, + @Parameter(name = MAPPING_NAME_PARAM, description = "String of what the mapping should be accessible under. If left empty, the name of the file without the ending will be used as name.") @RequestParam(value = MAPPING_NAME_PARAM, required = false, defaultValue = "") String name, @Parameter(name = ENCODING_PARAM, description = "The encoding of the file. Default is UTF-8.") @RequestParam(value = ENCODING_PARAM, required = false, defaultValue = "UTF-8") String encoding, - @Parameter(name = DELIMITER_PARAM, description = "The delimiter used in the file. Default is ','") @RequestParam(value = DELIMITER_PARAM, required = false, defaultValue = ",") char delimiter); + @Parameter(name = DELIMITER_PARAM, description = "The delimiter used in the file. Default is ','") @RequestParam(value = DELIMITER_PARAM, required = false, defaultValue = ",") String delimiter); @ResponseBody diff --git a/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml b/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml index 765dd7e1f..06502a1fd 100644 --- a/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml +++ b/persistence-service-v1/persistence-service-external-api-v2/src/main/resources/api/documine.yaml @@ -539,6 +539,7 @@ paths: parameters: - $ref: '#/components/parameters/dossierTemplateId' - $ref: '#/components/parameters/componentMappingId' + - $ref: '#/components/parameters/mappingName' - $ref: '#/components/parameters/encoding' - $ref: '#/components/parameters/delimiter' responses: diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingPersistenceService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingPersistenceService.java index 75ee77de7..9bbeb6e4a 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingPersistenceService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingPersistenceService.java @@ -6,9 +6,12 @@ import java.nio.file.Path; import java.time.OffsetDateTime; import java.util.List; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException; import com.iqser.red.service.persistence.management.v1.processor.model.ComponentMappingDownloadModel; import com.iqser.red.service.persistence.management.v1.processor.entity.ComponentMappingEntity; import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException; @@ -16,6 +19,7 @@ import com.iqser.red.service.persistence.management.v1.processor.service.persist import com.iqser.red.storage.commons.service.StorageService; import com.knecon.fforesight.tenantcommons.TenantContext; +import jakarta.validation.ConstraintViolationException; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -70,7 +74,11 @@ public class ComponentMappingPersistenceService { public void updateOrCreate(String storageId, File mappingFile, ComponentMappingEntity entity) { entity.setChangedDate(OffsetDateTime.now()); - repository.saveAndFlush(entity); + try { + repository.saveAndFlush(entity); + } catch (DataAccessException ex) { + throw new BadRequestException("Failed to save component mapping metadata due to invalid data.", ex); + } try (var in = new FileInputStream(mappingFile)) { storageService.storeObject(TenantContext.getTenantId(), storageId, in); } @@ -101,6 +109,7 @@ public class ComponentMappingPersistenceService { return outputFile; } + public void setVersion(String componentMappingId, Integer version) { repository.updateVersion(componentMappingId, version); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingService.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingService.java index 206632df3..c9c775b4b 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingService.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/ComponentMappingService.java @@ -62,11 +62,11 @@ public class ComponentMappingService { @SneakyThrows - public ComponentMappingMetadata update(String dossierTemplateId, String mappingId, String encoding, char delimiter, File mappingFile) { + public ComponentMappingMetadata update(String dossierTemplateId, String mappingId, String name, String encoding, char delimiter, File mappingFile) { ComponentMappingEntity entity = componentMappingPersistenceService.getEntityById(dossierTemplateId, mappingId); - return updateOrCreate(entity, encoding, delimiter, mappingFile); + return updateOrCreate(entity, name, encoding, delimiter, mappingFile); } @@ -83,21 +83,22 @@ public class ComponentMappingService { .id(id) .dossierTemplate(dossierTemplatePersistenceService.getDossierTemplate(dossierTemplateId)) .storageId(storageId) - .name(name) .fileName(fileName) .build(); - return updateOrCreate(entity, encoding, delimiter, mappingFile); + return updateOrCreate(entity, name, encoding, delimiter, mappingFile); + } @SneakyThrows - private ComponentMappingMetadata updateOrCreate(ComponentMappingEntity entity, String encoding, char delimiter, File mappingFile) { + private ComponentMappingMetadata updateOrCreate(ComponentMappingEntity entity, String name, String encoding, char delimiter, File mappingFile) { Charset charset = resolveCharset(encoding); CsvStats stats = sortCSVFile(delimiter, mappingFile, charset); + entity.setName(name); entity.setDelimiter(delimiter); entity.setEncoding(encoding); entity.setNumberOfLines(stats.numberOfLines()); @@ -140,10 +141,17 @@ public class ComponentMappingService { columnLabels = rows.remove(0); // remove header row - if (Arrays.stream(columnLabels).distinct().count() < columnLabels.length) { + if (Arrays.stream(columnLabels) + .distinct() + .count() < columnLabels.length) { throw new BadRequestException("Column labels may not contain duplicates!"); } + if (rows.stream() + .anyMatch(row -> row.length != columnLabels.length)) { + throw new BadRequestException("Data in csv must be rectangular!"); + } + numberOfLines = (int) reader.getLinesRead() - 1; // subtract header row rows.sort(CSV_SORTER); diff --git a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/DossierRepository.java b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/DossierRepository.java index 247d3c8bc..6a3b41d3b 100644 --- a/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/DossierRepository.java +++ b/persistence-service-v1/persistence-service-processor-v1/src/main/java/com/iqser/red/service/persistence/management/v1/processor/service/persistence/repository/DossierRepository.java @@ -86,7 +86,7 @@ public interface DossierRepository extends JpaRepository @Query("select d.id from DossierEntity d where d.dossierTemplateId = :dossierTemplateId") - Set findIdsByDossierTemplateId(String dossierTemplateId); + Set findIdsByDossierTemplateId(@Param("dossierTemplateId") String dossierTemplateId); @Modifying