diff --git a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java index dfc8126a9b86..3466ffa7fc6f 100644 --- a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java +++ b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/main/java/org/apache/nifi/processors/jolt/AbstractJoltTransform.java @@ -190,7 +190,15 @@ protected Collection customValidate(ValidationContext validati } } } catch (final Exception e) { - String message = String.format("Specification not valid for the selected transformation: %s", e.getMessage()); + final String reason; + final Throwable cause = e.getCause(); + if (cause == null) { + reason = e.getMessage(); + } else { + reason = "%s [%s]".formatted(cause.getMessage(), e.getMessage()); + } + + final String message = "Specification not valid... %s".formatted(reason); results.add(new ValidationResult.Builder() .valid(false) .subject(JOLT_SPEC.getDisplayName()) diff --git a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java index 8efe2e9db920..f5e2ebc0ee60 100644 --- a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java +++ b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/java/org/apache/nifi/processors/jolt/TestJoltTransformJSON.java @@ -18,6 +18,7 @@ import io.joltcommunity.jolt.Diffy; import io.joltcommunity.jolt.JsonUtils; +import org.apache.nifi.components.ValidationResult; import org.apache.nifi.flowfile.attributes.CoreAttributes; import org.apache.nifi.jolt.util.JoltTransformStrategy; import org.apache.nifi.processor.Processor; @@ -40,6 +41,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -109,6 +111,19 @@ void testInvalidJOLTSpec() { runner.assertValid(); } + @Test + void testNonExistingJOLTSpec() throws IOException { + final Path testResourcesDir = Paths.get("src/test/resources"); + final Path nonexistingSpec = testResourcesDir.resolve("nonExistingSpec.json"); + runner.setProperty(JoltTransformJSON.JOLT_SPEC, nonexistingSpec.toAbsolutePath().toString()); + runner.enqueue(JSON_INPUT); + runner.assertNotValid(); + + final Collection validationResults = runner.validate(); + final String explanation = validationResults.iterator().next().getExplanation(); + assertTrue(explanation.contains("file")); + } + @Test void testIncorrectJOLTSpec() { runner.setProperty(JoltTransformJSON.JOLT_SPEC, chainrSpecContents); @@ -211,10 +226,9 @@ void testCustomTransformationWithInvalidClassName() throws IOException { } @ParameterizedTest() - @MethodSource("getChainrArguments") - void testTransformInputWithChainr(Path specPath) throws IOException { - final String spec = Files.readString(specPath); - runner.setProperty(JoltTransformJSON.JOLT_SPEC, spec); + @MethodSource("getChainrArgumentContents") + void testTransformInputWithChainrContents(String chainrSpecContents) throws IOException { + runner.setProperty(JoltTransformJSON.JOLT_SPEC, chainrSpecContents); runner.enqueue(JSON_INPUT); runner.run(); @@ -615,9 +629,50 @@ private static Stream unicodeEscaping() { ); } - private static Stream getChainrArguments() { + private static Stream getChainrArgumentContents() throws IOException { + final String chainrSpecWithLeadingMultLineJavaComment = """ + /* + Multiline Java comment + */ + [ + { + "operation": "shift", + "spec": { + "rating": { + "primary": { + "value": "Rating", + "max": "RatingRange" + }, + "*": { + "max": "SecondaryRatings.&1.Range", + "value": "SecondaryRatings.&1.Value", + "$": "SecondaryRatings.&1.Id" + } + } + } + }, + { + "operation": "default", + "spec": { + "Range": 5, + "SecondaryRatings": { + "*": { + "Range": 5 + } + } + } + } + ] + """; + + final Path chainrSpecWithoutCommentsPath = Paths.get(CHAINR_SPEC_PATH); + final String chainrSpecWithoutComments = Files.readString(chainrSpecWithoutCommentsPath); + final Path chainrSpecWithLeadingSingleLineCommentPath = Paths.get("src/test/resources/specs/chainrSpecWithSingleLineComment.json"); + final String chainrSpecWithLeadingSingleLineComment = Files.readString(chainrSpecWithLeadingSingleLineCommentPath); + return Stream.of( - Arguments.argumentSet("has no single line comments", Paths.get(CHAINR_SPEC_PATH)), - Arguments.argumentSet("has a single line comment", Paths.get("src/test/resources/specs/chainrSpecWithSingleLineComment.json"))); + Arguments.argumentSet("No Java comments", chainrSpecWithoutComments), + Arguments.argumentSet("Leading single line Java comment", chainrSpecWithLeadingSingleLineComment), + Arguments.argumentSet("Leading multi-line Java comment", chainrSpecWithLeadingMultLineJavaComment)); } } diff --git a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json index 3b8d0d0b6b18..29a93cd147d8 100644 --- a/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json +++ b/nifi-extension-bundles/nifi-jolt-bundle/nifi-jolt-processors/src/test/resources/specs/chainrSpecWithSingleLineComment.json @@ -1,5 +1,5 @@ +// Single line comment [ - // Single line comment { "operation": "shift", "spec": { diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java index 9ee0450c7c3d..e5d1cbc7764e 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestValidateJson.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.processors.standard; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.json.schema.JsonSchema; import org.apache.nifi.json.schema.SchemaVersion; @@ -252,6 +253,120 @@ void testMultilineJsonWhereSecondLineInvalid(ValidateJson.InputFormat inputForma runner.clearTransferState(); } + @ParameterizedTest + @MethodSource("schemasWithLeadingComments") + void testSchemaContentWithLeadingComments(String schema) { + runner.setProperty(ValidateJson.SCHEMA_CONTENT, schema); + runner.setProperty(JsonSchemaRegistryComponent.SCHEMA_VERSION, SCHEMA_VERSION); + runner.enqueue(getFileContent("simple-example-with-comments.json")); + runner.assertValid(); + + final AssertionFailedError assertionFailedError = assertThrows(AssertionFailedError.class, () -> runner.run()); + final String stackTrace = ExceptionUtils.getStackTrace(assertionFailedError); + assertTrue(stackTrace.contains("JsonParseException") && !stackTrace.contains("FileNotFoundException")); + } + + @ParameterizedTest + @MethodSource("schemasWithEmbeddedComments") + void testSchemaContentWithEmbeddedComments(String schema) { + runner.setProperty(ValidateJson.SCHEMA_CONTENT, schema); + runner.setProperty(JsonSchemaRegistryComponent.SCHEMA_VERSION, SCHEMA_VERSION); + runner.enqueue(getFileContent("simple-example-with-comments.json")); + runner.assertValid(); + + final AssertionFailedError assertionFailedError = assertThrows(AssertionFailedError.class, () -> runner.run()); + final String stackTrace = ExceptionUtils.getStackTrace(assertionFailedError); + assertTrue(stackTrace.contains("JsonParseException")); + } + + @ParameterizedTest + @MethodSource("schemasWithTrailingComments") + void testSchemaContentWithTrailingCommentsIgnored(String schema) { + runner.setProperty(ValidateJson.SCHEMA_CONTENT, schema); + runner.setProperty(JsonSchemaRegistryComponent.SCHEMA_VERSION, SCHEMA_VERSION); + runner.enqueue(getFileContent("simple-example-with-comments.json")); + runner.assertValid(); + + runner.run(); + + runner.assertAllFlowFilesTransferred(ValidateJson.REL_VALID); + } + + private static Stream schemasWithLeadingComments() { + final String schemasWithSingleLineComment = """ + // Single line Java comment + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": true + } + """; + final String schemaWithMultiLineComment = """ + /* + Multi-line Java comment + */ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": true + } + """; + return Stream.of( + Arguments.argumentSet("Leading Java single line comment", schemasWithSingleLineComment), + Arguments.argumentSet("Leading Java multi-line comment", schemaWithMultiLineComment) + ); + } + + private static Stream schemasWithEmbeddedComments() { + final String schemasWithSingleLineComment = """ + { + // Single line Java comment + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": true + } + """; + final String schemaWithMultiLineComment = """ + { + /* + Multi-line Java comment + */ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": true + } + """; + return Stream.of( + Arguments.argumentSet("Embedded Java single line comment", schemasWithSingleLineComment), + Arguments.argumentSet("Embedded Java multi-line comment", schemaWithMultiLineComment) + ); + } + + private static Stream schemasWithTrailingComments() { + final String schemasWithSingleLineComment = """ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": true + } + // Single line Java comment + """; + final String schemaWithMultiLineComment = """ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": true + } + /* + Multi-line Java comment + */ + """; + return Stream.of( + Arguments.argumentSet("Trailing Java single line comment", schemasWithSingleLineComment), + Arguments.argumentSet("Trailing Java multi-line comment", schemaWithMultiLineComment) + ); + } + private void assertValidationErrors(Relationship relationship, boolean expected) { final Map attributes = runner.getFlowFilesForRelationship(relationship).getFirst().getAttributes();