From ae06dab2eb21caf7fd9e7a140df073504c952047 Mon Sep 17 00:00:00 2001 From: Jan van Mansum Date: Sat, 30 Nov 2024 21:25:13 +0100 Subject: [PATCH] wip --- pom.xml | 1 + .../DdValidateDansBagApplication.java | 8 +-- .../core/service/FileServiceImpl.java | 12 +++- .../core/service/RuleEngineService.java | 2 + .../core/service/RuleEngineServiceImpl.java | 57 ++++++++++++++++++- .../ValidateLocalDirApiResource.java | 42 ++++++++++++++ .../ValidateOkYamlMessageBodyWriter.java | 50 ---------------- .../resources/ValidateZipApiResource.java | 47 +++++++++++++++ .../ValidateResourceIntegrationTest.java | 30 ---------- 9 files changed, 160 insertions(+), 89 deletions(-) create mode 100644 src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateLocalDirApiResource.java delete mode 100644 src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateOkYamlMessageBodyWriter.java create mode 100644 src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateZipApiResource.java diff --git a/pom.xml b/pom.xml index 12174752..4759491f 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ nl.knaw.dans.validatedansbag.DdValidateDansBagApplication + 0.5.1-SNAPSHOT diff --git a/src/main/java/nl/knaw/dans/validatedansbag/DdValidateDansBagApplication.java b/src/main/java/nl/knaw/dans/validatedansbag/DdValidateDansBagApplication.java index 773d8f58..cf72965a 100644 --- a/src/main/java/nl/knaw/dans/validatedansbag/DdValidateDansBagApplication.java +++ b/src/main/java/nl/knaw/dans/validatedansbag/DdValidateDansBagApplication.java @@ -16,7 +16,6 @@ package nl.knaw.dans.validatedansbag; -import io.dropwizard.configuration.FileConfigurationSourceProvider; import io.dropwizard.core.Application; import io.dropwizard.core.setup.Bootstrap; import io.dropwizard.core.setup.Environment; @@ -45,8 +44,9 @@ import nl.knaw.dans.validatedansbag.core.validator.PolygonListValidatorImpl; import nl.knaw.dans.validatedansbag.health.XmlSchemaHealthCheck; import nl.knaw.dans.validatedansbag.resources.IllegalArgumentExceptionMapper; -import nl.knaw.dans.validatedansbag.resources.ValidateOkYamlMessageBodyWriter; +import nl.knaw.dans.validatedansbag.resources.ValidateLocalDirApiResource; import nl.knaw.dans.validatedansbag.resources.ValidateResource; +import nl.knaw.dans.validatedansbag.resources.ValidateZipApiResource; import nl.knaw.dans.vaultcatalog.client.invoker.ApiClient; import nl.knaw.dans.vaultcatalog.client.resources.DefaultApi; @@ -55,7 +55,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -125,7 +124,8 @@ public void run(final DdValidateDansBagConfiguration configuration, final Enviro environment.jersey().register(new IllegalArgumentExceptionMapper()); environment.jersey().register(new ValidateResource(ruleEngineService, fileService)); - environment.jersey().register(new ValidateOkYamlMessageBodyWriter()); + environment.jersey().register(new ValidateZipApiResource(ruleEngineService, fileService)); + environment.jersey().register(new ValidateLocalDirApiResource(ruleEngineService)); environment.healthChecks().register("xml-schemas", new XmlSchemaHealthCheck(xmlSchemaValidator)); } diff --git a/src/main/java/nl/knaw/dans/validatedansbag/core/service/FileServiceImpl.java b/src/main/java/nl/knaw/dans/validatedansbag/core/service/FileServiceImpl.java index 1a90acec..53c6326d 100644 --- a/src/main/java/nl/knaw/dans/validatedansbag/core/service/FileServiceImpl.java +++ b/src/main/java/nl/knaw/dans/validatedansbag/core/service/FileServiceImpl.java @@ -32,9 +32,17 @@ public class FileServiceImpl implements FileService { private final Path baseFolder; + private final Path tempFolder; public FileServiceImpl(Path baseFolder) { - this.baseFolder = baseFolder; + this.baseFolder = baseFolder.normalize().toAbsolutePath(); + + try { + this.tempFolder = Files.createDirectories(this.baseFolder.resolve("temp")); + } + catch (IOException e) { + throw new RuntimeException("Could not create temp directory", e); + } } @Override @@ -84,7 +92,7 @@ public CharBuffer readFileContents(Path path, Charset charset) throws IOExceptio @Override public Path extractZipFile(InputStream inputStream) throws IOException { - var tempPath = Files.createTempDirectory(this.baseFolder, "bag-"); + var tempPath = Files.createTempDirectory(this.tempFolder, "bag-"); try (var input = new ZipInputStream(inputStream)) { var entry = input.getNextEntry(); diff --git a/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineService.java b/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineService.java index 59aeb5ca..53b42a81 100644 --- a/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineService.java +++ b/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineService.java @@ -15,6 +15,7 @@ */ package nl.knaw.dans.validatedansbag.core.service; +import nl.knaw.dans.validatedansbag.api.ValidateOkDto; import nl.knaw.dans.validatedansbag.core.engine.DepositType; import nl.knaw.dans.validatedansbag.core.engine.RuleValidationResult; @@ -25,4 +26,5 @@ public interface RuleEngineService { List validateBag(Path path, DepositType depositType) throws Exception; + ValidateOkDto validateBag(Path path, DepositType depositType, String bagLocation) throws Exception; } diff --git a/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineServiceImpl.java b/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineServiceImpl.java index d593ee52..be23d1e8 100644 --- a/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineServiceImpl.java +++ b/src/main/java/nl/knaw/dans/validatedansbag/core/service/RuleEngineServiceImpl.java @@ -16,6 +16,8 @@ package nl.knaw.dans.validatedansbag.core.service; import lombok.extern.slf4j.Slf4j; +import nl.knaw.dans.validatedansbag.api.ValidateOkDto; +import nl.knaw.dans.validatedansbag.api.ValidateOkRuleViolationsInnerDto; import nl.knaw.dans.validatedansbag.core.BagNotFoundException; import nl.knaw.dans.validatedansbag.core.engine.DepositType; import nl.knaw.dans.validatedansbag.core.engine.NumberedRule; @@ -25,6 +27,7 @@ import java.nio.file.Path; import java.util.List; +import java.util.stream.Collectors; @Slf4j public class RuleEngineServiceImpl implements RuleEngineService { @@ -34,8 +37,8 @@ public class RuleEngineServiceImpl implements RuleEngineService { private final NumberedRule[] ruleSet; public RuleEngineServiceImpl(RuleEngine ruleEngine, - FileService fileService, - NumberedRule[] ruleSet) { + FileService fileService, + NumberedRule[] ruleSet) { this.ruleEngine = ruleEngine; this.fileService = fileService; this.ruleSet = ruleSet; @@ -54,10 +57,58 @@ public List validateBag(Path path, DepositType depositType return ruleEngine.validateRules(path, this.ruleSet, depositType); } + @Override + public ValidateOkDto validateBag(Path path, DepositType depositType, String bagLocation) throws Exception { + log.info("Validating bag on path '{}'", path); + + if (!fileService.isReadable(path)) { + log.warn("Path {} could not not be found or is not readable", path); + throw new BagNotFoundException(String.format("Bag on path '%s' could not be found or read", path)); + } + + var results = ruleEngine.validateRules(path, this.ruleSet, depositType); + var isValid = results.stream().noneMatch(r -> r.getStatus().equals(RuleValidationResult.RuleValidationResultStatus.FAILURE)); + + var result = new ValidateOkDto(); + result.setBagLocation(bagLocation); + result.setIsCompliant(isValid); + result.setName(path.getFileName().toString()); + result.setProfileVersion("1.1.0"); + result.setInformationPackageType(toInfoPackageType(depositType)); + result.setRuleViolations(results.stream() + .filter(r -> r.getStatus().equals(RuleValidationResult.RuleValidationResultStatus.FAILURE)) + .map(rule -> { + var ret = new ValidateOkRuleViolationsInnerDto(); + ret.setRule(rule.getNumber()); + + var message = new StringBuilder(); + + if (rule.getErrorMessage() != null) { + message.append(rule.getErrorMessage()); + } + + ret.setViolation(message.toString()); + return ret; + }) + .collect(Collectors.toList())); + + log.debug("Validation result: {}", result); + + return result; + } + + private ValidateOkDto.InformationPackageTypeEnum toInfoPackageType(DepositType value) { + if (DepositType.MIGRATION.equals(value)) { + return ValidateOkDto.InformationPackageTypeEnum.MIGRATION; + } + return ValidateOkDto.InformationPackageTypeEnum.DEPOSIT; + } + public void validateRuleConfiguration() { try { this.ruleEngine.validateRuleConfiguration(this.ruleSet); - } catch (RuleEngineConfigurationException e) { + } + catch (RuleEngineConfigurationException e) { throw new RuntimeException("Rule configuration is not valid", e); } } diff --git a/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateLocalDirApiResource.java b/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateLocalDirApiResource.java new file mode 100644 index 00000000..e9375992 --- /dev/null +++ b/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateLocalDirApiResource.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 DANS - Data Archiving and Networked Services (info@dans.knaw.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.knaw.dans.validatedansbag.resources; + +import lombok.AllArgsConstructor; +import nl.knaw.dans.validatedansbag.api.ValidateCommandDto; +import nl.knaw.dans.validatedansbag.core.engine.DepositType; +import nl.knaw.dans.validatedansbag.core.service.RuleEngineService; + +import javax.ws.rs.core.Response; +import java.nio.file.Path; + +@AllArgsConstructor +public class ValidateLocalDirApiResource implements ValidateLocalDirApi { + private final RuleEngineService ruleEngineService; + + @Override + public Response validateLocalDirPost(ValidateCommandDto validateCommandDto) { + try { + var result = ruleEngineService.validateBag(Path.of(validateCommandDto.getBagLocation()), + DepositType.valueOf(validateCommandDto.getPackageType().toString()), + validateCommandDto.getBagLocation()); + return Response.ok(result).build(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateOkYamlMessageBodyWriter.java b/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateOkYamlMessageBodyWriter.java deleted file mode 100644 index 25453c8b..00000000 --- a/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateOkYamlMessageBodyWriter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022 DANS - Data Archiving and Networked Services (info@dans.knaw.nl) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package nl.knaw.dans.validatedansbag.resources; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import nl.knaw.dans.validatedansbag.api.ValidateOkDto; - -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; - -@Provider -@Produces(MediaType.TEXT_PLAIN) -public class ValidateOkYamlMessageBodyWriter implements MessageBodyWriter { - private final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); - - @Override - public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { - return type == ValidateOkDto.class; - } - - @Override - public void writeTo(ValidateOkDto ValidateOkDtoResult, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, - OutputStream entityStream) throws IOException, WebApplicationException { - var result = objectMapper.writeValueAsString(ValidateOkDtoResult); - entityStream.write(result.getBytes(StandardCharsets.UTF_8)); - } -} diff --git a/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateZipApiResource.java b/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateZipApiResource.java new file mode 100644 index 00000000..3089c1d5 --- /dev/null +++ b/src/main/java/nl/knaw/dans/validatedansbag/resources/ValidateZipApiResource.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 DANS - Data Archiving and Networked Services (info@dans.knaw.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.knaw.dans.validatedansbag.resources; + +import lombok.AllArgsConstructor; +import nl.knaw.dans.validatedansbag.core.engine.DepositType; +import nl.knaw.dans.validatedansbag.core.service.FileService; +import nl.knaw.dans.validatedansbag.core.service.RuleEngineService; + +import javax.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; + +@AllArgsConstructor +public class ValidateZipApiResource implements ValidateZipApi { + private final RuleEngineService ruleEngineService; + private final FileService fileService; + + @Override + public Response validateZipPost(File body) { + try (var inputStream = new FileInputStream(body)) { + var dir = fileService.extractZipFile(inputStream); + var bagDir = fileService.getFirstDirectory(dir); + if (bagDir.isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST).entity("No bag directory found in zip file").build(); + } + var result = ruleEngineService.validateBag(bagDir.get(), DepositType.DEPOSIT, "ZIP"); + return Response.ok(result).build(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/nl/knaw/dans/validatedansbag/resources/ValidateResourceIntegrationTest.java b/src/test/java/nl/knaw/dans/validatedansbag/resources/ValidateResourceIntegrationTest.java index 860ffef9..5f9c7a22 100644 --- a/src/test/java/nl/knaw/dans/validatedansbag/resources/ValidateResourceIntegrationTest.java +++ b/src/test/java/nl/knaw/dans/validatedansbag/resources/ValidateResourceIntegrationTest.java @@ -91,7 +91,6 @@ public boolean isValidLicense(String license) { static { EXT = ResourceExtension.builder() .addProvider(MultiPartFeature.class) - .addProvider(ValidateOkYamlMessageBodyWriter.class) .addResource(buildValidateResource()) .build(); } @@ -367,35 +366,6 @@ void validateFormData_should_not_throw_internal_server_error_on_incomplete_manif .endsWith("original-metadata.zip] is in the payload directory but isn't listed in any manifest!"); } - @Test - void validateZipFile_should_return_a_textual_representation_when_requested() throws Exception { - var inputStream = Files.newInputStream(Path.of(baseTestFolder, "/zips/invalid-sha1.zip")); - - var embargoResultJson = """ - { - "status": "OK", - "data": { - "message": "24" - } - }"""; - var maxEmbargoDurationResult = new MockedDataverseResponse(embargoResultJson, DataMessage.class); - Mockito.when(dataverseService.getMaxEmbargoDurationInMonths()) - .thenReturn(maxEmbargoDurationResult); - - var response = EXT.target("/validate") - .request() - .header("accept", "text/plain") - .header("Authorization", basicUsernamePassword("user001", "user001")) - .post(Entity.entity(inputStream, MediaType.valueOf("application/zip")), String.class); - - assertTrue(response.contains("Bag location:")); - assertTrue(response.contains("Name:")); - assertTrue(response.contains("Profile version:")); - assertTrue(response.contains("Information package type:")); - assertTrue(response.contains("Is compliant:")); - assertTrue(response.contains("Rule violations:")); - } - @Test void validateFormData_with_invalid_path_should_return_400_error() { var data = new ValidateCommandDto();