Fix for issues/766 - reverse references with _history in GraphQL (#790)

* Create and use getIdPart

* Add tests and changes for dstu3, r4, r4b, and r5

* Update get/set Id javadoc

Co-authored-by: dotasek <david.otasek@smilecdr.com>
This commit is contained in:
dotasek 2022-04-22 18:06:24 -04:00 committed by GitHub
parent 23efd7ba33
commit 1645982389
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 201 additions and 58 deletions

View File

@ -4,4 +4,5 @@
## Other code changes
* no changes
* Fix reverse references in GraphQL searches

View File

@ -111,16 +111,25 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
*/
/**
* @return The id value of the resource. Once assigned, this value never
* changes.
*
* @see IdType
* @see IdType#getValue()
*/
public String getId() {
return this.id == null ? null : this.id.getValue();
}
/**
* @param value The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
*/
/**
* @return The most complete id value of the resource, containing all
* available context and history. Once assigned this value never changes.
* NOTE: this value is NOT limited to just the logical id property of a
* resource id.
* @see IdType
* @see IdType#getValue()
*/
public Resource setId(String value) {
if (Utilities.noString(value))
this.id = null;
@ -132,6 +141,14 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return the logical ID part of this resource's id
* @see IdType#getIdPart()
*/
public String getIdPart() {
return getIdElement().getIdPart();
}
/**
* @return {@link #meta} (The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content may not always be associated with version changes to the resource.)
*/

View File

@ -643,7 +643,7 @@ public class GraphQLEngine implements IGraphQLEngine {
Argument arg = new Argument();
params.add(arg);
arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getIdPart()));
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), params, list);
arg = null;
ObjectValue obj = null;

View File

@ -117,16 +117,25 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
*/
/**
* @return The most complete id value of the resource, containing all
* available context and history. Once assigned this value never changes.
* NOTE: this value is NOT limited to just the logical id property of a
* resource id.
* @see IdType
* @see IdType#getValue()
*/
public String getId() {
return this.id == null ? null : this.id.getValue();
}
/**
* @param value The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
*/
/**
* @param value The id value of the resource. Once assigned, this value
* never changes.
*
* @see IdType
* @see IdType#setValue(String)
*/
public Resource setId(String value) {
if (Utilities.noString(value))
this.id = null;
@ -138,6 +147,14 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return the logical ID part of this resource's id
* @see IdType#getIdPart()
*/
public String getIdPart() {
return getIdElement().getIdPart();
}
/**
* @return {@link #meta} (The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.)
*/

View File

@ -65,7 +65,7 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
public class TestingUtilities {
private static final boolean SHOW_DIFF = true;
private static final boolean SHOW_DIFF = false;
static public IWorkerContext fcontext;
@ -74,7 +74,7 @@ public class TestingUtilities {
FilesystemPackageCacheManager pcm;
try {
pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
fcontext = SimpleWorkerContext.fromPackage(pcm.loadPackage("hl7.fhir.core", "4.0.0"));
fcontext = SimpleWorkerContext.fromPackage(pcm.loadPackage("hl7.fhir.r4.core", "4.0.1"));
fcontext.setUcumService(new UcumEssenceService(TestingUtilities.resourceNameToFile("ucum", "ucum-essence.xml")));
fcontext.setExpansionProfile(new Parameters());
} catch (Exception e) {

View File

@ -507,7 +507,7 @@ public class GraphQLEngine implements IGraphQLEngine {
Argument arg = new Argument();
params.add(arg);
arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType() + "/" + source.getId()));
arg.addValue(new StringValue(source.fhirType() + "/" + source.getIdPart()));
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), params, list);
arg = null;
ObjectValue obj = null;

View File

@ -14,13 +14,11 @@ import org.hl7.fhir.r4.test.utils.TestingUtilities;
import org.hl7.fhir.r4.utils.GraphQLEngine;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.NameValue;
import org.hl7.fhir.utilities.graphql.Parser;
import org.hl7.fhir.utilities.graphql.*;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -37,7 +35,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@Disabled
public class GraphQLEngineTests implements IGraphQLStorageServices {
public static Stream<Arguments> data() throws FileNotFoundException, IOException, ParserConfigurationException, SAXException {
@ -52,6 +50,7 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
return objects.stream();
}
@Disabled
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("data")
public void test(String name, String source, String output, String context, String resource, String operation) throws Exception {
@ -66,12 +65,35 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase() + "-" + parts[1].toLowerCase() + ".xml");
}
Resource parsedResource = !Utilities.noString( filename ) ? new XmlParser().parse(new FileInputStream(filename)) : null;
testResource(parsedResource, output, source);
}
@Test
public void testReferenceReverseHistory() throws IOException, EGraphEngine, EGraphQLException {
String context = "Patient/example/$graphql";
String source = "reference-reverse.gql";
String output= "reference-reverse-history.json";
String[] parts = context.split("/");
String filename = TestingUtilities.resourceNameToFile(parts[0].toLowerCase() + "-" + parts[1].toLowerCase() + ".xml");
Resource parsedResource = new XmlParser().parse(new FileInputStream(filename));
parsedResource.setId("example/_history/1");
testResource(parsedResource, output, source);
}
private void testResource(Resource resource, String output, String source) throws IOException, EGraphEngine, EGraphQLException {
GraphQLEngine gql = new GraphQLEngine(TestingUtilities.context());
gql.setServices(this);
if (!Utilities.noString(filename))
gql.setFocus(new XmlParser().parse(new FileInputStream(filename)));
if (resource != null) {
gql.setFocus(resource);
}
gql.setGraphQL(Parser.parseFile(TestingUtilities.resourceNameToFile("graphql", source)));
gql.getGraphQL().setOperationName(operation);
gql.getGraphQL().setOperationName(null);
gql.getGraphQL().getVariables().add(new Argument("var", new NameValue("true")));
boolean ok = false;
String msg = null;
@ -134,8 +156,9 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
@Override
public void listResources(Object appInfo, String type, List<Argument> searchParams, List<IBaseResource> matches) throws FHIRException {
try {
if (type.equals("Condition"))
if (type.equals("Condition") && searchParams.get(0).hasValue("Patient/example"))
matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("condition-example.xml"))));
else if (type.equals("Patient")) {
matches.add(new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("patient-example.xml"))));

View File

@ -0,0 +1,6 @@
{
"id":"example/_history/1",
"ConditionList":[{
"id":"example"
}]
}

View File

@ -119,15 +119,24 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
*/
/**
* @return The most complete id value of the resource, containing all
* available context and history. Once assigned this value never changes.
* NOTE: this value is NOT limited to just the logical id property of a
* resource id.
* @see IdType
* @see IdType#getValue()
*/
public String getId() {
return this.id == null ? null : this.id.getValue();
}
/**
* @param value The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
* @param value The id value of the resource. Once assigned, this value
* never changes.
*
* @see IdType
* @see IdType#setValue(String)
*/
public Resource setId(String value) {
if (Utilities.noString(value))
@ -140,6 +149,14 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return the logical ID part of this resource's id
* @see IdType#getIdPart()
*/
public String getIdPart() {
return getIdElement().getIdPart();
}
/**
* @return {@link #meta} (The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.)
*/

View File

@ -674,7 +674,7 @@ public class GraphQLEngine implements IGraphQLEngine {
Argument arg = new Argument();
params.add(arg);
arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getIdPart()));
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), params, list);
arg = null;
ObjectValue obj = null;

View File

@ -24,12 +24,11 @@ import org.hl7.fhir.r4b.test.utils.TestingUtilities;
import org.hl7.fhir.r4b.utils.GraphQLEngine;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.NameValue;
import org.hl7.fhir.utilities.graphql.Parser;
import org.hl7.fhir.utilities.graphql.*;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -65,10 +64,17 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
stream = TestingUtilities.loadTestResourceStream("r4b", parts[0].toLowerCase()+"-"+parts[1].toLowerCase()+".xml");
}
Resource parsedResource = stream != null ? new XmlParser().parse(stream) : null;
testResource(parsedResource, output, source, operation);
}
private void testResource(Resource resource, String output, String source, String operation) throws IOException, EGraphEngine, EGraphQLException {
GraphQLEngine gql = new GraphQLEngine(TestingUtilities.context());
gql.setServices(this);
if (stream != null)
gql.setFocus(new XmlParser().parse(stream));
if (resource != null) {
gql.setFocus(resource);
}
gql.setGraphQL(Parser.parse(TestingUtilities.loadTestResource("r4b", "graphql", source)));
gql.getGraphQL().setOperationName(operation);
gql.getGraphQL().getVariables().add(new Argument("var", new NameValue("true")));
@ -85,12 +91,12 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
}
if (ok) {
Assertions.assertTrue(!output.equals("$error"), "Expected to fail, but didn't");
StringBuilder str = new StringBuilder();
StringBuilder actualStringBuilder = new StringBuilder();
gql.getOutput().setWriteWrapper(false);
gql.getOutput().write(str, 0);
gql.getOutput().write(actualStringBuilder, 0);
IOUtils.copy(TestingUtilities.loadTestResourceStream("r4b", "graphql", source), new FileOutputStream(TestingUtilities.tempFile("graphql", source)));
IOUtils.copy(TestingUtilities.loadTestResourceStream("r4b", "graphql", output), new FileOutputStream(TestingUtilities.tempFile("graphql", output)));
TextFile.stringToFile(str.toString(), TestingUtilities.tempFile("graphql", output+".out"));
TextFile.stringToFile(actualStringBuilder.toString(), TestingUtilities.tempFile("graphql", output+".out"));
msg = TestingUtilities.checkJsonIsSame(TestingUtilities.tempFile("graphql", output+".out"), TestingUtilities.tempFile("graphql", output));
Assertions.assertTrue(Utilities.noString(msg), msg);
}
@ -98,6 +104,23 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
Assertions.assertTrue(output.equals("$error"), "Error, but proper output was expected ("+msg+")");
}
@Test
public void testReferenceReverseHistory() throws Exception {
String context = "Patient/example/$graphql";
String source = "reference-reverse.gql";
String output="reference-reverse-history.json";
String[] parts = context.split("/");
InputStream stream = TestingUtilities.loadTestResourceStream("r4b", parts[0].toLowerCase()+"-"+parts[1].toLowerCase()+".xml");
Resource parsedResource = new XmlParser().parse(stream);
//Rather than duplicate the entire resource we modify the ID with a _history path
parsedResource.setId("example/_history/1");
testResource(parsedResource, output, source, null);
}
@Override
public Resource lookup(Object appInfo, String type, String id) throws FHIRException {
try {
@ -136,7 +159,7 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
@Override
public void listResources(Object appInfo, String type, List<Argument> searchParams, List<IBaseResource> matches) throws FHIRException {
try {
if (type.equals("Condition"))
if (type.equals("Condition") && searchParams.get(0).hasValue("Patient/example"))
matches.add(new XmlParser().parse(TestingUtilities.loadTestResourceStream("r4b", "condition-example.xml")));
else if (type.equals("Patient")) {
matches.add(new XmlParser().parse(TestingUtilities.loadTestResourceStream("r4b", "patient-example.xml")));

View File

@ -119,16 +119,25 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
*/
/**
* @return The most complete id value of the resource, containing all
* available context and history. Once assigned this value never changes.
* NOTE: this value is NOT limited to just the logical id property of a
* resource id.
* @see IdType
* @see IdType#getValue()
*/
public String getId() {
return this.id == null ? null : this.id.getValue();
}
/**
* @param value The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.
*/
/**
* @param value The id value of the resource. Once assigned, this value
* never changes.
*
* @see IdType
* @see IdType#setValue(String)
*/
public Resource setId(String value) {
if (Utilities.noString(value))
this.id = null;
@ -140,6 +149,14 @@ public abstract class Resource extends BaseResource implements IAnyResource {
return this;
}
/**
* @return the logical ID part of this resource's id
* @see IdType#getIdPart()
*/
public String getIdPart() {
return getIdElement().getIdPart();
}
/**
* @return {@link #meta} (The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content might not always be associated with version changes to the resource.)
*/
@ -414,8 +431,8 @@ public abstract class Resource extends BaseResource implements IAnyResource {
, language);
}
// Manual code (from Configuration.txt):
@Override
// Manual code (from Configuration.txt):
@Override
public String getIdBase() {
return getId();
}

View File

@ -673,7 +673,7 @@ public class GraphQLEngine implements IGraphQLEngine {
Argument arg = new Argument();
params.add(arg);
arg.setName(getSingleValue(parg));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
arg.addValue(new StringValue(source.fhirType()+"/"+source.getIdPart()));
services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), params, list);
arg = null;
ObjectValue obj = null;

View File

@ -25,12 +25,10 @@ import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.GraphQLEngine;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.NameValue;
import org.hl7.fhir.utilities.graphql.Parser;
import org.hl7.fhir.utilities.graphql.*;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -66,10 +64,16 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
stream = TestingUtilities.loadTestResourceStream("r5", parts[0].toLowerCase()+"-"+parts[1].toLowerCase()+".xml");
}
Resource parsedResource = stream != null ? new XmlParser().parse(stream) : null;
testResource(parsedResource, output, source, operation);
}
private void testResource(Resource resource, String output, String source, String operation) throws IOException, EGraphEngine, EGraphQLException {
GraphQLEngine gql = new GraphQLEngine(TestingUtilities.getSharedWorkerContext());
gql.setServices(this);
if (stream != null)
gql.setFocus(new XmlParser().parse(stream));
if (resource != null)
gql.setFocus(resource);
gql.setGraphQL(Parser.parse(TestingUtilities.loadTestResource("r5", "graphql", source)));
gql.getGraphQL().setOperationName(operation);
gql.getGraphQL().getVariables().add(new Argument("var", new NameValue("true")));
@ -97,6 +101,24 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
}
else
Assertions.assertTrue(output.equals("$error"), "Error, but proper output was expected ("+msg+")");
}
@Test
public void testReferenceReverseHistory() throws Exception {
String context = "Patient/example/$graphql";
String source = "reference-reverse.gql";
String output="reference-reverse-history.json";
String[] parts = context.split("/");
InputStream stream = TestingUtilities.loadTestResourceStream("r5", parts[0].toLowerCase()+"-"+parts[1].toLowerCase()+".xml");
Resource parsedResource = new XmlParser().parse(stream);
//Rather than duplicate the entire resource we modify the ID with a _history path
parsedResource.setId("example/_history/1");
testResource(parsedResource, output, source, null);
}
@Override
@ -137,7 +159,7 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
@Override
public void listResources(Object appInfo, String type, List<Argument> searchParams, List<IBaseResource> matches) throws FHIRException {
try {
if (type.equals("Condition"))
if (type.equals("Condition") && searchParams.get(0).hasValue("Patient/example"))
matches.add(new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "condition-example.xml")));
else if (type.equals("Patient")) {
matches.add(new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "patient-example.xml")));

View File

@ -19,7 +19,7 @@
<properties>
<hapi_fhir_version>5.4.0</hapi_fhir_version>
<validator_test_case_version>1.1.97</validator_test_case_version>
<validator_test_case_version>1.1.98-SNAPSHOT</validator_test_case_version>
<junit_jupiter_version>5.7.1</junit_jupiter_version>
<junit_platform_launcher_version>1.7.1</junit_platform_launcher_version>
<maven_surefire_version>3.0.0-M5</maven_surefire_version>