rework r4 RDF tests to skip fewer examples (#2159)
+ relative URL resolution in RDFParser + test non-DomainResources in RDFParserTest ~ factor/organize RDFParserTest ~ avoid EXTERNAL shapes (for now) in shex-java - disable the bundle-response.json example as it is apparently malformed + added urn:-guard to PreResourceStateHl7Org.wereBack ID update
This commit is contained in:
parent
8a0b0885f2
commit
8b338cd168
|
@ -1260,13 +1260,15 @@ class ParserState<T> {
|
||||||
String versionId = elem.getMeta().getVersionId();
|
String versionId = elem.getMeta().getVersionId();
|
||||||
if (StringUtils.isBlank(elem.getIdElement().getIdPart())) {
|
if (StringUtils.isBlank(elem.getIdElement().getIdPart())) {
|
||||||
// Resource has no ID
|
// Resource has no ID
|
||||||
} else if (StringUtils.isNotBlank(versionId)) {
|
} else if (!elem.getIdElement().getIdPart().startsWith("urn:")) {
|
||||||
|
if (StringUtils.isNotBlank(versionId)) {
|
||||||
elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart() + "/_history/" + versionId);
|
elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart() + "/_history/" + versionId);
|
||||||
} else {
|
} else {
|
||||||
elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart());
|
elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.jena.datatypes.xsd.XSDDatatype;
|
import org.apache.jena.datatypes.xsd.XSDDatatype;
|
||||||
import org.apache.jena.rdf.model.*;
|
import org.apache.jena.rdf.model.*;
|
||||||
import org.apache.jena.riot.Lang;
|
import org.apache.jena.riot.Lang;
|
||||||
|
import org.apache.jena.riot.system.IRIResolver;
|
||||||
import org.apache.jena.vocabulary.RDF;
|
import org.apache.jena.vocabulary.RDF;
|
||||||
import org.hl7.fhir.instance.model.api.*;
|
import org.hl7.fhir.instance.model.api.*;
|
||||||
|
|
||||||
|
@ -171,10 +172,14 @@ public class RDFParser extends BaseParser {
|
||||||
if (!uriBase.endsWith("/")) {
|
if (!uriBase.endsWith("/")) {
|
||||||
uriBase = uriBase + "/";
|
uriBase = uriBase + "/";
|
||||||
}
|
}
|
||||||
String resourceUri = uriBase + resource.getIdElement().toUnqualified();
|
|
||||||
|
|
||||||
if (parentResource == null) {
|
if (parentResource == null) {
|
||||||
|
if (!resource.getIdElement().toUnqualified().hasIdPart()) {
|
||||||
|
parentResource = rdfModel.getResource(null);
|
||||||
|
} else {
|
||||||
|
String resourceUri = IRIResolver.resolve(resource.getIdElement().toUnqualified().toString(), uriBase).toString();
|
||||||
parentResource = rdfModel.getResource(resourceUri);
|
parentResource = rdfModel.getResource(resourceUri);
|
||||||
|
}
|
||||||
// If the resource already exists and has statements, return that existing resource.
|
// If the resource already exists and has statements, return that existing resource.
|
||||||
if (parentResource != null && parentResource.listProperties().toList().size() > 0) {
|
if (parentResource != null && parentResource.listProperties().toList().size() > 0) {
|
||||||
return parentResource;
|
return parentResource;
|
||||||
|
|
|
@ -9,15 +9,14 @@ import fr.inria.lille.shexjava.schema.parsing.GenParser;
|
||||||
import fr.inria.lille.shexjava.validation.RecursiveValidation;
|
import fr.inria.lille.shexjava.validation.RecursiveValidation;
|
||||||
import fr.inria.lille.shexjava.validation.ValidationAlgorithm;
|
import fr.inria.lille.shexjava.validation.ValidationAlgorithm;
|
||||||
import org.apache.commons.rdf.api.Graph;
|
import org.apache.commons.rdf.api.Graph;
|
||||||
import org.apache.commons.rdf.api.IRI;
|
import org.apache.commons.rdf.api.RDFTerm;
|
||||||
import org.apache.commons.rdf.rdf4j.RDF4J;
|
import org.apache.commons.rdf.rdf4j.RDF4J;
|
||||||
import org.eclipse.rdf4j.model.Model;
|
import org.eclipse.rdf4j.model.Model;
|
||||||
import org.eclipse.rdf4j.model.impl.SimpleIRI;
|
|
||||||
import org.eclipse.rdf4j.rio.RDFFormat;
|
import org.eclipse.rdf4j.rio.RDFFormat;
|
||||||
import org.eclipse.rdf4j.rio.Rio;
|
import org.eclipse.rdf4j.rio.Rio;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.Base;
|
import org.hl7.fhir.r4.model.Base;
|
||||||
import org.hl7.fhir.r4.model.DomainResource;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
@ -25,8 +24,10 @@ import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
@ -53,6 +54,15 @@ public class RDFParserTest extends BaseTest {
|
||||||
fhirSchema = GenParser.parseSchema(schemaFile, Collections.emptyList());
|
fhirSchema = GenParser.parseSchema(schemaFile, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we can't round-trip JSON, we skip the Turtle round-trip test.
|
||||||
|
private static ArrayList<String> jsonRoundTripErrors = new ArrayList<String>();
|
||||||
|
@AfterAll
|
||||||
|
static void reportJsonRoundTripErrors() {
|
||||||
|
System.out.println(jsonRoundTripErrors.size() + " tests disqualified because of JSON round-trip errors");
|
||||||
|
for (String e : jsonRoundTripErrors)
|
||||||
|
System.out.println(e);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This test method has a method source for each JSON file in the resources/rdf-test-input directory (see #getInputFiles).
|
* This test method has a method source for each JSON file in the resources/rdf-test-input directory (see #getInputFiles).
|
||||||
* Each input file is expected to be a JSON representation of an R4 FHIR resource.
|
* Each input file is expected to be a JSON representation of an R4 FHIR resource.
|
||||||
|
@ -62,35 +72,124 @@ public class RDFParserTest extends BaseTest {
|
||||||
* 3. Perform a graph validation on the resulting RDF using ShEx and ShEx-java -- ensure validation passed
|
* 3. Perform a graph validation on the resulting RDF using ShEx and ShEx-java -- ensure validation passed
|
||||||
* 4. Parse the RDF string into the HAPI object model -- ensure resource instance is not null
|
* 4. Parse the RDF string into the HAPI object model -- ensure resource instance is not null
|
||||||
* 5. Perform deep equals comparison of JSON-originated instance and RDF-originated instance -- ensure equality
|
* 5. Perform deep equals comparison of JSON-originated instance and RDF-originated instance -- ensure equality
|
||||||
* @param inputFile -- path to resource file to be tested
|
* @param referenceFilePath -- path to resource file to be tested
|
||||||
* @throws IOException -- thrown when parsing RDF string into graph model
|
* @throws IOException -- thrown when parsing RDF string into graph model
|
||||||
*/
|
*/
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("getInputFiles")
|
@MethodSource("getInputFiles")
|
||||||
public void testRDFRoundTrip(String inputFile) throws IOException {
|
public void testRDFRoundTrip(String referenceFilePath) throws IOException {
|
||||||
FileInputStream inputStream = new FileInputStream(inputFile);
|
String referenceFileName = referenceFilePath.substring(referenceFilePath.lastIndexOf("/")+1);
|
||||||
IBaseResource resource;
|
IBaseResource referenceResource = parseJson(new FileInputStream(referenceFilePath));
|
||||||
String resourceType;
|
String referenceJson = serializeJson(ourCtx, referenceResource);
|
||||||
// Parse JSON input as Resource
|
|
||||||
resource = ourCtx.newJsonParser().parseResource(inputStream);
|
|
||||||
assertNotNull(resource);
|
|
||||||
resourceType = resource.fhirType();
|
|
||||||
|
|
||||||
// Write the resource out to an RDF String
|
|
||||||
String rdfContent = ourCtx.newRDFParser().encodeResourceToString(resource);
|
|
||||||
assertNotNull(rdfContent);
|
|
||||||
|
|
||||||
// Perform ShEx validation on RDF
|
// Perform ShEx validation on RDF
|
||||||
|
String turtleString = serializeRdf(ourCtx, referenceResource);
|
||||||
|
validateRdf(turtleString, referenceFileName, referenceResource);
|
||||||
|
|
||||||
|
// If we can round-trip JSON
|
||||||
|
IBaseResource viaJsonResource = parseJson(new ByteArrayInputStream(referenceJson.getBytes()));
|
||||||
|
if (((Base)viaJsonResource).equalsDeep((Base)referenceResource)) {
|
||||||
|
|
||||||
|
// Parse RDF content as resource
|
||||||
|
IBaseResource viaTurtleResource = parseRdf(ourCtx, new StringReader(turtleString));
|
||||||
|
assertNotNull(viaTurtleResource);
|
||||||
|
|
||||||
|
// Compare original JSON-based resource against RDF-based resource
|
||||||
|
String viaTurtleJson = serializeJson(ourCtx, viaTurtleResource);
|
||||||
|
if (!((Base)viaTurtleResource).equalsDeep((Base)referenceResource)) {
|
||||||
|
String failMessage = referenceFileName + ": failed to round-trip Turtle ";
|
||||||
|
if (referenceJson.equals(viaTurtleJson))
|
||||||
|
throw new Error(failMessage
|
||||||
|
+ "\nttl: " + turtleString
|
||||||
|
+ "\nexp: " + referenceJson);
|
||||||
|
else
|
||||||
|
assertEquals(referenceJson, viaTurtleJson, failMessage + "\nttl: " + turtleString);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String gotString = serializeJson(ourCtx, viaJsonResource);
|
||||||
|
String skipMessage = referenceFileName + ": failed to round-trip JSON" +
|
||||||
|
(referenceJson.equals(gotString)
|
||||||
|
? "\ngot: " + gotString + "\nexp: " + referenceJson
|
||||||
|
: "\nsome inequality not visible in: " + referenceJson);
|
||||||
|
System.out.println(referenceFileName + " skipped");
|
||||||
|
// Specific messages are printed at end of run.
|
||||||
|
jsonRoundTripErrors.add(skipMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<String> getInputFiles() throws IOException {
|
||||||
|
ClassLoader cl = RDFParserTest.class.getClassLoader();
|
||||||
|
List<String> resourceList = new ArrayList<>();
|
||||||
|
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
|
||||||
|
Resource[] resources = resolver.getResources("classpath:rdf-test-input/*.json") ;
|
||||||
|
for (Resource resource: resources)
|
||||||
|
resourceList.add(resource.getFile().getPath());
|
||||||
|
|
||||||
|
return resourceList.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON functions
|
||||||
|
public IBaseResource parseJson(InputStream inputStream) {
|
||||||
|
IParser refParser = ourCtx.newJsonParser();
|
||||||
|
refParser.setStripVersionsFromReferences(false);
|
||||||
|
// parser.setDontStripVersionsFromReferencesAtPaths();
|
||||||
|
IBaseResource ret = refParser.parseResource(inputStream);
|
||||||
|
assertNotNull(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String serializeJson(FhirContext ctx, IBaseResource resource) {
|
||||||
|
IParser jsonParser = ctx.newJsonParser();
|
||||||
|
jsonParser.setStripVersionsFromReferences(false);
|
||||||
|
String ret = jsonParser.encodeResourceToString(resource);
|
||||||
|
assertNotNull(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rdf (Turtle) functions
|
||||||
|
public IBaseResource parseRdf(FhirContext ctx, StringReader inputStream) {
|
||||||
|
IParser refParser = ctx.newRDFParser();
|
||||||
|
IBaseResource ret = refParser.parseResource(inputStream);
|
||||||
|
assertNotNull(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String serializeRdf(FhirContext ctx, IBaseResource resource) {
|
||||||
|
IParser rdfParser = ourCtx.newRDFParser();
|
||||||
|
rdfParser.setStripVersionsFromReferences(false);
|
||||||
|
rdfParser.setServerBaseUrl("http://a.example/fhir/");
|
||||||
|
String ret = rdfParser.encodeResourceToString(resource);
|
||||||
|
assertNotNull(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateRdf(String rdfContent, String referenceFileName, IBaseResource referenceResource) throws IOException {
|
||||||
|
String baseIRI = "http://a.example/shex/";
|
||||||
RDF4J factory = new RDF4J();
|
RDF4J factory = new RDF4J();
|
||||||
GlobalFactory.RDFFactory = factory; //set the global factory used in shexjava
|
GlobalFactory.RDFFactory = factory; //set the global factory used in shexjava
|
||||||
|
|
||||||
// load the model
|
|
||||||
String baseIRI = "http://a.example.shex/";
|
|
||||||
Model data = Rio.parse(new StringReader(rdfContent), baseIRI, RDFFormat.TURTLE);
|
Model data = Rio.parse(new StringReader(rdfContent), baseIRI, RDFFormat.TURTLE);
|
||||||
|
FixedShapeMapEntry fixedMapEntry = new FixedShapeMapEntry(factory, data, referenceResource.fhirType(), baseIRI);
|
||||||
|
Graph dataGraph = factory.asGraph(data); // create the graph
|
||||||
|
ValidationAlgorithm validation = new RecursiveValidation(fhirSchema, dataGraph);
|
||||||
|
validation.validate(fixedMapEntry.node, fixedMapEntry.shape);
|
||||||
|
boolean result = validation.getTyping().isConformant(fixedMapEntry.node, fixedMapEntry.shape);
|
||||||
|
assertTrue(result,
|
||||||
|
referenceFileName + ": failed to validate " + fixedMapEntry
|
||||||
|
+ "\n" + referenceFileName
|
||||||
|
+ "\n" + rdfContent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shape Expressions functions
|
||||||
|
class FixedShapeMapEntry {
|
||||||
|
RDFTerm node;
|
||||||
|
Label shape;
|
||||||
|
|
||||||
|
FixedShapeMapEntry(RDF4J factory, Model data, String resourceType, String baseIRI) {
|
||||||
String rootSubjectIri = null;
|
String rootSubjectIri = null;
|
||||||
|
// StmtIterator i = data.listStatements();
|
||||||
for (org.eclipse.rdf4j.model.Resource resourceStream : data.subjects()) {
|
for (org.eclipse.rdf4j.model.Resource resourceStream : data.subjects()) {
|
||||||
if (resourceStream instanceof SimpleIRI) {
|
// if (resourceStream instanceof SimpleIRI) {
|
||||||
Model filteredModel = data.filter(resourceStream, factory.getValueFactory().createIRI(NODE_ROLE_IRI), factory.getValueFactory().createIRI(TREE_ROOT_IRI), (org.eclipse.rdf4j.model.Resource)null);
|
Model filteredModel = data.filter(resourceStream, factory.getValueFactory().createIRI(NODE_ROLE_IRI), factory.getValueFactory().createIRI(TREE_ROOT_IRI), (org.eclipse.rdf4j.model.Resource)null);
|
||||||
if (filteredModel != null && filteredModel.subjects().size() == 1) {
|
if (filteredModel != null && filteredModel.subjects().size() == 1) {
|
||||||
Optional<org.eclipse.rdf4j.model.Resource> rootResource = filteredModel.subjects().stream().findFirst();
|
Optional<org.eclipse.rdf4j.model.Resource> rootResource = filteredModel.subjects().stream().findFirst();
|
||||||
|
@ -100,46 +199,21 @@ public class RDFParserTest extends BaseTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// create the graph
|
|
||||||
Graph dataGraph = factory.asGraph(data);
|
|
||||||
|
|
||||||
// choose focus node and shapelabel
|
// choose focus node and shapelabel
|
||||||
IRI focusNode = factory.createIRI(rootSubjectIri);
|
this.node = rootSubjectIri.indexOf(":") == -1
|
||||||
|
? factory.createBlankNode(rootSubjectIri)
|
||||||
|
: factory.createIRI(rootSubjectIri);
|
||||||
Label shapeLabel = new Label(factory.createIRI(FHIR_SHAPE_PREFIX + resourceType));
|
Label shapeLabel = new Label(factory.createIRI(FHIR_SHAPE_PREFIX + resourceType));
|
||||||
|
// this.node = focusNode;
|
||||||
ValidationAlgorithm validation = new RecursiveValidation(fhirSchema, dataGraph);
|
this.shape = shapeLabel;
|
||||||
validation.validate(focusNode, shapeLabel);
|
|
||||||
boolean result = validation.getTyping().isConformant(focusNode, shapeLabel);
|
|
||||||
assertTrue(result);
|
|
||||||
|
|
||||||
// Parse RDF content as resource
|
|
||||||
IBaseResource parsedResource = ourCtx.newRDFParser().parseResource(new StringReader(rdfContent));
|
|
||||||
assertNotNull(parsedResource);
|
|
||||||
|
|
||||||
// Compare original JSON-based resource against RDF-based resource
|
|
||||||
if (parsedResource instanceof DomainResource) {
|
|
||||||
// This is a hack because this initializes the collection if it is empty
|
|
||||||
((DomainResource) parsedResource).getContained();
|
|
||||||
boolean deepEquals = ((Base)parsedResource).equalsDeep((Base)resource);
|
|
||||||
assertTrue(deepEquals);
|
|
||||||
} else {
|
|
||||||
ourLog.warn("Input JSON did not yield a DomainResource");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<String> getInputFiles() throws IOException {
|
public String toString() {
|
||||||
ClassLoader cl = RDFParserTest.class.getClassLoader();
|
return "<" + node.toString() + ">@" + shape.toPrettyString();
|
||||||
List<String> resourceList = new ArrayList<>();
|
|
||||||
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
|
|
||||||
Resource[] resources = resolver.getResources("classpath:rdf-test-input/*.json") ;
|
|
||||||
for (Resource resource: resources){
|
|
||||||
resourceList.add(resource.getFile().getPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resourceList.stream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Why is this disabled?
|
||||||
|
|
||||||
|
| file | reason |
|
||||||
|
| - | - |
|
||||||
|
| [bundle-response](bundle-response.json) | [entry\[0\].response.outcome.issue](bundle-response.json#L50-L61) is not in [R4 Resource](http://hl7.org/fhir/resource.html#Resource) |
|
|
@ -13267,7 +13267,7 @@ fhirvs:contact-point-use ["home" "work" "temp" "old" "mobile"]
|
||||||
fhirvs:immunization-status ["completed" "entered-in-error" "not-done"]
|
fhirvs:immunization-status ["completed" "entered-in-error" "not-done"]
|
||||||
|
|
||||||
# This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)
|
# This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)
|
||||||
fhirvs:mimetypes EXTERNAL
|
fhirvs:mimetypes .
|
||||||
|
|
||||||
# The use of an address.
|
# The use of an address.
|
||||||
fhirvs:address-use ["home" "work" "temp" "old" "billing"]
|
fhirvs:address-use ["home" "work" "temp" "old" "billing"]
|
||||||
|
|
Loading…
Reference in New Issue