Merge branch 'RED-9375' into 'master'

RED-9375: fix component mapping endpoint validation

Closes RED-9375

See merge request redactmanager/persistence-service!561
This commit is contained in:
Kilian Schüttler 2024-06-27 09:42:21 +02:00
commit c34af58aa0
6 changed files with 76 additions and 19 deletions

View File

@ -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);

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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);

View File

@ -86,7 +86,7 @@ public interface DossierRepository extends JpaRepository<DossierEntity, String>
@Query("select d.id from DossierEntity d where d.dossierTemplateId = :dossierTemplateId")
Set<String> findIdsByDossierTemplateId(String dossierTemplateId);
Set<String> findIdsByDossierTemplateId(@Param("dossierTemplateId") String dossierTemplateId);
@Modifying