diff --git a/models/ivoa/vo-dml/ivoa_base.vodml-binding.xml b/models/ivoa/vo-dml/ivoa_base.vodml-binding.xml
index bf4f8d72..6128cfed 100644
--- a/models/ivoa/vo-dml/ivoa_base.vodml-binding.xml
+++ b/models/ivoa/vo-dml/ivoa_base.vodml-binding.xml
@@ -7,6 +7,7 @@
org.ivoa.dm
http://ivoa.net/vodml/ivoa
http://ivoa.net/dm/models/vo-dml/xsd/ivoa
+
real
Double
diff --git a/models/sample/test/lifecycleTest.vo-dml.xml b/models/sample/test/lifecycleTest.vo-dml.xml
index 265a8f74..30283dab 100644
--- a/models/sample/test/lifecycleTest.vo-dml.xml
+++ b/models/sample/test/lifecycleTest.vo-dml.xml
@@ -9,10 +9,9 @@
pharriso
0.1
- 2024-11-21T13:44:44Z
+ 2024-12-19T13:11:08Z
- ivoa
- 1.0
+ null
IVOA-v1.0.vo-dml.xml
not known
@@ -66,18 +65,6 @@
1
-
- Contained.refbad2
- refbad2
-
-
- lifecycleTest:ReferredLifeCycle
-
-
- 1
- 1
-
-
ATest
diff --git a/models/sample/test/lifecycleTest.vodsl b/models/sample/test/lifecycleTest.vodsl
index b63728c9..cd57b31d 100644
--- a/models/sample/test/lifecycleTest.vodsl
+++ b/models/sample/test/lifecycleTest.vodsl
@@ -16,7 +16,12 @@
otype Contained "" {
test2: ivoa:string "";
- refbad2 references ReferredLifeCycle "";/* this is bad because one of the places that Contained could be contained - i.e Atest3 is not in the ReferredLifeCycle containment hierarchy */
+/* below is *really* bad (i.e. breaks the generated code json serialization intention as the
+ it them moves where the referred to things are apparently contained - as the JSON referencing mechanism
+ currently in place will output the real object first and then the reference - so it need to come across the "contained"
+ object first - TODO try to express this restriction in schematron.
+*/
+// refbad2 references ReferredLifeCycle "";/* this is bad because one of the places that Contained could be contained - i.e Atest3 is not in the ReferredLifeCycle containment hierarchy */
}
otype ATest { //this does things as per current rules
diff --git a/runtime/java/build.gradle.kts b/runtime/java/build.gradle.kts
index f0ce146d..fc21d3bb 100644
--- a/runtime/java/build.gradle.kts
+++ b/runtime/java/build.gradle.kts
@@ -16,7 +16,7 @@ dependencies {
// implementation("org.glassfish.jaxb:jaxb-runtime:2.3.6")
implementation("jakarta.persistence:jakarta.persistence-api:3.1.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0")
- implementation("com.networknt:json-schema-validator:1.5.3")
+ implementation("com.networknt:json-schema-validator:1.5.4")
implementation("org.hibernate.orm:hibernate-core:6.5.3.Final")
implementation("org.slf4j:slf4j-api:1.7.36")
diff --git a/runtime/java/src/main/java/org/ivoa/vodml/ModelDescription.java b/runtime/java/src/main/java/org/ivoa/vodml/ModelDescription.java
index 9197b668..faaada5b 100644
--- a/runtime/java/src/main/java/org/ivoa/vodml/ModelDescription.java
+++ b/runtime/java/src/main/java/org/ivoa/vodml/ModelDescription.java
@@ -38,6 +38,12 @@ public interface ModelDescription {
*/
String xmlNamespace();
+ /**
+ * The json schema.
+ * @return the jsonSchema at the head of the model
+ */
+ String jsonSchema();
+
/**
* Get the classes that make up content.
* @return the list of classes.
diff --git a/runtime/java/src/main/java/org/ivoa/vodml/validation/AbstractBaseValidation.java b/runtime/java/src/main/java/org/ivoa/vodml/validation/AbstractBaseValidation.java
index e7cb07a4..f4e43c6c 100644
--- a/runtime/java/src/main/java/org/ivoa/vodml/validation/AbstractBaseValidation.java
+++ b/runtime/java/src/main/java/org/ivoa/vodml/validation/AbstractBaseValidation.java
@@ -9,6 +9,7 @@
import java.util.HashMap;
import java.util.Map;
+import com.networknt.schema.output.OutputUnit;
import jakarta.persistence.EntityManager;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
@@ -61,8 +62,13 @@ protected RoundTripResult roundTripJSON(VodmlModel m) throws JsonProc
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(model);
System.out.println("JSON output");
System.out.println(json);
+ JSONValidator jsonValidator = new JSONValidator(m.management());
+ OutputUnit vresult = jsonValidator.validate(json);
+ if (!vresult.isValid()) {
+ System.err.println(vresult.toString());
+ }
T retval = mapper.readValue(json, clazz);
- return new RoundTripResult(true, retval);
+ return new RoundTripResult(vresult.isValid(), retval);
}
diff --git a/runtime/java/src/main/java/org/ivoa/vodml/validation/JSONValidator.java b/runtime/java/src/main/java/org/ivoa/vodml/validation/JSONValidator.java
new file mode 100644
index 00000000..bd73eb84
--- /dev/null
+++ b/runtime/java/src/main/java/org/ivoa/vodml/validation/JSONValidator.java
@@ -0,0 +1,65 @@
+package org.ivoa.vodml.validation;
+
+
+/*
+ * Created on 17/12/2024 by Paul Harrison (paul.harrison@manchester.ac.uk).
+ */
+
+import com.networknt.schema.*;
+import com.networknt.schema.output.OutputUnit;
+import org.ivoa.vodml.ModelManagement;
+
+import java.util.Set;
+
+/**
+ * Validate JSON against the generated schema.
+ */
+public class JSONValidator {
+
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory
+ .getLogger(JSONValidator.class);
+ private final ModelManagement> modelManagement;
+ private final SchemaValidatorsConfig config;
+ private JsonSchemaFactory jsonSchemaFactory;
+
+ /**
+ * create a validator for a particular model.
+ * @param modelManagement the model against which the validator should be created.
+ */
+ public JSONValidator(ModelManagement> modelManagement) {
+
+ this.modelManagement = modelManagement;
+ // This creates a schema factory that will use Draft 2020-12 as the default if $schema is not specified
+// in the schema data. If $schema is specified in the schema data then that schema dialect will be used
+// instead and this version is ignored.
+
+ jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012, builder ->
+ // This creates a mapping from $id which starts with https://www.example.org/ to the retrieval URI classpath:
+ builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://ivoa.net/dm/", "classpath:"))
+ );
+
+ SchemaValidatorsConfig.Builder builder = SchemaValidatorsConfig.builder();
+// By default the JDK regular expression implementation which is not ECMA 262 compliant is used
+// Note that setting this requires including optional dependencies
+// builder.regularExpressionFactory(GraalJSRegularExpressionFactory.getInstance());
+// builder.regularExpressionFactory(JoniRegularExpressionFactory.getInstance());
+ config = builder.build();
+
+ }
+
+ /**
+ * validate some JSON against the model schema.
+ * @param json the input JSON as a string.
+ * @return the validation result.
+ */
+ public OutputUnit validate(String json) {
+ JsonSchema schema = jsonSchemaFactory.getSchema(SchemaLocation.of(modelManagement.description().jsonSchema()), config);
+
+ OutputUnit ou = schema.validate(json, InputFormat.JSON, OutputFormat.HIERARCHICAL, executionContext -> {
+ // By default since Draft 2019-09 the format keyword only generates annotations and not assertions
+ executionContext.getExecutionConfig().setFormatAssertionsEnabled(true);
+ });
+ return ou;
+ }
+}
+
diff --git a/tools/gradletooling/TODO.md b/tools/gradletooling/TODO.md
index 4943d616..98de56c9 100644
--- a/tools/gradletooling/TODO.md
+++ b/tools/gradletooling/TODO.md
@@ -120,6 +120,7 @@ VODML Tooling TODO
* allow refs to be serialized/deserialized as ids always.... - for use in APIs.... https://stackoverflow.com/questions/51172496/how-to-dynamically-ignore-a-property-on-jackson-serialization
* perhaps have custom written ivoa base schema.... express some better rules... e.g. non neg integer...
* modern usage https://blogs.oracle.com/javamagazine/post/java-json-serialization-jackson
+ * It might be best to generate schema even for imported models as it is difficult to decide what to do for `"additionalProperties": false` without context of how a particular type is being derived from.
# Python production
diff --git a/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifeCycleDetailedTest.java b/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifeCycleDetailedTest.java
index 285d2ad9..6ec27582 100644
--- a/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifeCycleDetailedTest.java
+++ b/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifeCycleDetailedTest.java
@@ -47,7 +47,7 @@ void setUp() throws Exception {
List refcont =
Arrays.asList(new ReferredLifeCycle("rc1"), new ReferredLifeCycle("rc2"));
List contained =
- Arrays.asList(new Contained("firstcontained", refcont.get(0)), new Contained("secondContained", refcont.get(1)));
+ Arrays.asList(new Contained("firstcontained"), new Contained("secondContained"));
atest =
ATest.createATest(
diff --git a/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifecycleTestModelTest.java b/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifecycleTestModelTest.java
index d33ee2e4..bb6ab1b9 100644
--- a/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifecycleTestModelTest.java
+++ b/tools/gradletooling/sample/src/test/java/org/ivoa/dm/lifecycle/LifecycleTestModelTest.java
@@ -59,7 +59,7 @@ public LifecycleTestModel createModel() {
List refcont =
Arrays.asList(new ReferredLifeCycle("rc1"), new ReferredLifeCycle("rc2"));
List contained =
- Arrays.asList(new Contained("firstcontained", refcont.get(0)), new Contained("secondContained", refcont.get(1)));
+ Arrays.asList(new Contained("firstcontained"), new Contained("secondContained"));
atest =
ATest.createATest(
diff --git a/tools/xslt/jaxb.xsl b/tools/xslt/jaxb.xsl
index fe12e979..79ffa073 100644
--- a/tools/xslt/jaxb.xsl
+++ b/tools/xslt/jaxb.xsl
@@ -563,6 +563,11 @@
}
+ @Override
+ public String jsonSchema() {
+ return "";
+ }
+
/**
* Return a list of content classes for this model.
* @return the list.
diff --git a/tools/xslt/vo-dml2jsonschema.xsl b/tools/xslt/vo-dml2jsonschema.xsl
index b91630b8..0b72a52a 100644
--- a/tools/xslt/vo-dml2jsonschema.xsl
+++ b/tools/xslt/vo-dml2jsonschema.xsl
@@ -35,9 +35,11 @@ that allow for successful JSON round tripping.
-
+
+
+
Generating JSON - considering models
@@ -61,7 +63,7 @@ that allow for successful JSON round tripping.
}
-
+ ,"additionalProperties": false
}
}
@@ -86,16 +88,19 @@ that allow for successful JSON round tripping.
"type" : "object"
,"properties" : {
"$comment" : "placeholder to make commas easier!"
-
+
,"" : {
"type": "array"
,"items" : {
-
+ "oneOf" : [
+ {},
+ {}
+ ]
}
}
}
-
+ ,"additionalProperties": false
}
@@ -111,7 +116,7 @@ that allow for successful JSON round tripping.
]
}
-
+ ,"additionalProperties": false
}
@@ -139,7 +144,15 @@ that allow for successful JSON round tripping.
- ,"additionalProperties": false
+
+
+ ,"additionalProperties" : false
+
+
+ ,"unevaluatedProperties" : false
+
+
+
@@ -155,9 +168,9 @@ that allow for successful JSON round tripping.
,
,"properties" : {
"$comment" : "placeholder to make commas easier!"
-
+
,"@type" : { "type": "string"}
-
+
@@ -186,6 +199,7 @@ that allow for successful JSON round tripping.
,
,"properties" : {
"$comment" : "placeholder to make commas easier!"
+ ,"@type" : { "type": "string"}
}
@@ -239,12 +253,22 @@ that allow for successful JSON round tripping.
-
+
, "" : {
,
}
+
+
+ , "" : {
+ "type":"array"
+ ,"items": {
+
+ }
+ ,
+ }
+
@@ -265,6 +289,21 @@ that allow for successful JSON round tripping.
+
+ ,"" : {
+ "type":"object"
+
+
+ ,"anyOf": [
+
+ ]
+
+
+ ,
+
+
+ }
+
,"" : {
@@ -284,7 +323,20 @@ that allow for successful JSON round tripping.
}
-
+
+
+ , "" : {
+ "type":"array"
+ ,"items": {
+
+ }
+ ,
+ }
+
+
+
+
, "" : {
"oneOf" : [
{},
diff --git a/xsd/vo-dml-binding.xsd b/xsd/vo-dml-binding.xsd
index 505523be..fe8d3813 100644
--- a/xsd/vo-dml-binding.xsd
+++ b/xsd/vo-dml-binding.xsd
@@ -42,12 +42,17 @@ targetNamespace="http://www.ivoa.net/xml/vodml-binding/v0.9.1" attributeFormDefa
-
+
+ customization of the xml serialization
+
+
-
- whether compositions with multiplicities greater than 1 should have a wrapper element
-
-
+
+
+ whether compositions with multiplicities greater than 1 should have a wrapper element
+
+
+
@@ -57,6 +62,21 @@ targetNamespace="http://www.ivoa.net/xml/vodml-binding/v0.9.1" attributeFormDefa
+
+
+ customization of the JSON serialization
+
+
+
+
+ whether definitions in the schema model should be "closed off" with "additionalProperties": false - if a type definition is expected to be extended, then this cannot be done
+
+The default is false which indicates that types should be "closed off".
+
+In general if a model is expected to be used a a "base model" then this should probably be set to true for the model.
+
+
+