0

I am using popular JSON schema library - json-schema-validator - for checking incoming payloads against a schema of my own.

The following is summary of my grief:

private final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
    private JsonSchema preparedSchema;
    public ReportByTeamCriteriaSchemaChecker(String schemaLocationPath) {
        try {
            JsonNode schemaNode = JsonLoader.fromPath(schemaLocationPath);
            this.preparedSchema = this.factory.getJsonSchema(schemaNode);
        } catch (ProcessingException | IOException e) {
            e.printStackTrace();
        }
    }

There isn't any problem, till here. The Schema file is found in its designated location, and the schemaNode is prepared.

Given an incoming JSONString (the payload, to be more definite), this is how I confirm its correctness against the schema loaded earlier:

private Try<ProcessingReport>  checkAgainstSchema(JsonSchema schema, String jsonifiedString) {

   Try<ProcessingReport> result =
      Try
      .of(() ->  JsonLoader.fromString(jsonifiedString))
      .mapTry( (jsonNode) -> schema.validate(jsonNode));

        return (result);
    }

This function, is called thus:

Try<String> syntaxParsingresult =
     this.checkAgainstSchema(this.preparedSchema, jsonifiedString)
    .map(e -> extractErrorMessagesFromReport(e));

The relevant portion of the build.gradle file:

sourceCompatibility = '1.9'
targetCompatibility = '1.9'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

if (!hasProperty('mainClass')) {
    //ext.mainClass = 'test.NewMain'
    mainClassName = "drivers.ReportServerEntryPoint"
}

repositories {
    mavenCentral()
    maven { url "http://maven.restlet.org" }
}
ext.restletVersion = "2.3.10"

// ........

 // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'
    // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.8'
    // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.8'
    // https://mvnrepository.com/artifact/commons-io/commons-io
    compile group: 'commons-io', name: 'commons-io', version: '2.6'
    compile(group: "com.github.java-json-tools", name: "json-schema-validator", version: "2.2.10")
    // https://mvnrepository.com/artifact/io.vavr/vavr
    compile group: 'io.vavr', name: 'vavr', version: '0.9.2'

This works perfectly fine (including all testcases) when run from inside IntelliJ IDEA. However, when I build the JAR and run from the command line, the exception thrown is very confusing:

enter image description here

The source-line that is found offensive by JVM (ReportByTeamCriteriaSchemaChecker.java:59) corresponds to the the function call mentioned earlier:

schema.validate(jsonNode)

I am using vavr - and thus, I suspected initially if vavr and json-schema-validator and Jackson were uncomfortable being together - but I have confirmed that even when I don't use vavr's Try, the exception is thrown at the runtime.

I referred to this conversation on StackOverflow, but is of very little use to me.

I am using JDK 1.9:

java version "9.0.1"
Java(TM) SE Runtime Environment (build 9.0.1+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

Could anybody, familiar with JSON Validation, point me to the right direction?


Update:

As an experiment, I added the following to the dependencies:

    dependencies {
      compile(group: "com.github.fge", name: "jackson-coreutils", version: "1.8");
    }

In this case, when I run the JAR from command line, the error message is exactly reversed:

Caused by: java.lang.IncompatibleClassChangeError: Found interface com.github.fge.jsonschema.main.JsonSchema, but class was expected
    at com.oneHuddle.application.utility.schemaChecker.ReportByTeamCriteriaSchemaChecker.validateAgainstSchema(ReportByTeamCriteriaSchemaChecker.java:59)
    at com.oneHuddle.application.ReportByTeamResource.lambda$getDefaultTeamReport$0(ReportByTeamResource.java:60)
    at io.vavr.control.Either.flatMap(Either.java:331)
    at com.oneHuddle.application.ReportByTeamResource.getDefaultTeamReport(ReportByTeamResource.java:59)

If anything, I am more confused! :-) Why should it run alright from inside IntelliJ but throw this error when an executable JAR is run from the command line?

Nirmalya
  • 717
  • 9
  • 19
  • 1
    According to the exception, there are incompatible binary changes that causes the error (see https://stackoverflow.com/questions/1980452/what-causes-java-lang-incompatibleclasschangeerror). Maybe it works in the tests as you have different dependencies there? – Philipp Claßen Dec 21 '18 at 04:50
  • 1
    @Phillipp , Good point, but I make two observations: (a) test dependencies are only on JUnit and Hamcrest, neither of which happens to be uncomfortable being along with JSON-related stuff and (b) I am running the app from inside IntelliJ (not the tests only, they pass anyway) from the menu option. So, the question is at runtime, what does IntelliJ know that an executable JAR doesn't? I will explore a bit further. – Nirmalya Dec 21 '18 at 04:59

1 Answers1

1

By resorting to the following class,

com.github.fge.jsonschema.main.JsonValidator

I have been able to sidestep the issue.

The current code looks like this:

private final JsonValidator validator = 
    JsonSchemaFactory.byDefault().getValidator();
private  JsonNode   schemaNode;
public ReportByTeamCriteriaSchemaChecker(String schemaLocationPath) {
    try {
        this.schemaNode = JsonLoader.fromPath(schemaLocationPath);
    } catch (IOException e) {
         e.printStackTrace();
    }
}

The function, which was using schema earlier, now uses the validator instead:

private Try<ProcessingReport>  
validatePayload(
     JsonNode schemaNode, String payload) {

   Try<ProcessingReport> result =
      Try
      .of(()->  JsonLoader.fromString(payload))
      .mapTry((jsonNode)  ->  
             this.validator.validate(schemaNode,jsonNode));

   return (result);

}

The controlling function behaves like it did, in the previous avatar:

 public Either<Tuple2<Enum<ReportByTeamExecutionStatus>,String>,String>
    validateAgainstSchema(String jsonifiedString) {

       Try<String> syntaxParsingresult =
               this.validateAgainstSchema(this.schemaNode, jsonifiedString)
               .map(e -> extractErrorMessagesFromReport(e));
// ..... rest of the logic

I have experimented with 3-4 possibilities, including:

  • forced refresh of all dependencies
  • creating a scan report of gradle
  • playing around with few different versions of Jackson and other dependent libraries

but none of them worked, but this one did.

Just to clarify, I have not found out what was the exact difference between IntelliJ's view of runtimeclasspath and gradle command's view of the same. Obviously, they resolve the dependencies in subtly (or not so subtly) different ways. But, I didn't have time.

I thought I should post my solution, just in case it helps someone.

Nirmalya
  • 717
  • 9
  • 19