Parser enhancements when parsing bundles

This commit is contained in:
jamesagnew 2016-04-05 08:59:24 -04:00
parent 891dddff1e
commit 079f966086
6 changed files with 284 additions and 82 deletions

View File

@ -35,10 +35,12 @@ import javax.xml.stream.events.XMLEvent;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseElement;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
@ -49,6 +51,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
@ -84,6 +87,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.IModelVisitor;
@ -2073,8 +2077,6 @@ class ParserState<T> {
@Override
public void wereBack() {
final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName());
if (myContext.hasDefaultTypeForProfile()) {
IBaseMetaType meta = myInstance.getMeta();
Class<? extends IBaseResource> wantedProfileType = null;
@ -2149,26 +2151,27 @@ class ParserState<T> {
}
});
final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName());
if (bundle) {
/*
* Stitch together resource references
*/
Map<IdDt, IResource> idToResource = new HashMap<IdDt, IResource>();
Map<IIdType, IBaseResource> idToResource = new HashMap<IIdType, IBaseResource>();
FhirTerser t = myContext.newTerser();
List<IResource> resources = t.getAllPopulatedChildElementsOfType(myInstance, IResource.class);
for (IResource next : resources) {
IdDt id = next.getId();
List<IBaseResource> resources = t.getAllPopulatedChildElementsOfType(myInstance, IBaseResource.class);
for (IBaseResource next : resources) {
IIdType id = next.getIdElement();
if (id != null && id.isEmpty() == false) {
String resName = myContext.getResourceDefinition(next).getName();
idToResource.put(id.withResourceType(resName).toUnqualifiedVersionless(), next);
}
}
for (IResource next : resources) {
List<BaseResourceReferenceDt> refs = myContext.newTerser().getAllPopulatedChildElementsOfType(next, BaseResourceReferenceDt.class);
for (BaseResourceReferenceDt nextRef : refs) {
if (nextRef.isEmpty() == false && nextRef.getReference() != null) {
IResource target = idToResource.get(nextRef.getReference().toUnqualifiedVersionless());
for (IBaseResource next : resources) {
List<IBaseReference> refs = myContext.newTerser().getAllPopulatedChildElementsOfType(next, IBaseReference.class);
for (IBaseReference nextRef : refs) {
if (nextRef.isEmpty() == false && nextRef.getReferenceElement() != null) {
IBaseResource target = idToResource.get(nextRef.getReferenceElement().toUnqualifiedVersionless());
if (target != null) {
nextRef.setResource(target);
}
@ -2176,6 +2179,18 @@ class ParserState<T> {
}
}
/*
* Set resource IDs based on Bundle.entry.request.url
*/
List<Pair<String, IBaseResource>> urlsAndResources = BundleUtil.getBundleEntryUrlsAndResources(myContext, (IBaseBundle) myInstance);
for (Pair<String, IBaseResource> pair : urlsAndResources) {
if (pair.getRight() != null && isNotBlank(pair.getLeft()) && pair.getRight().getIdElement().isEmpty()) {
if (pair.getLeft().startsWith("urn:")) {
pair.getRight().setId(pair.getLeft());
}
}
}
}
populateTarget();

View File

@ -23,12 +23,15 @@ package ca.uhn.fhir.util;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -58,4 +61,41 @@ public class BundleUtil {
return retVal;
}
@SuppressWarnings("unchecked")
public static List<Pair<String, IBaseResource>> getBundleEntryUrlsAndResources(FhirContext theContext, IBaseBundle theBundle) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestDef = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url");
List<Pair<String, IBaseResource>> retVal = new ArrayList<Pair<String,IBaseResource>>(entries.size());
for (IBase nextEntry : entries) {
String url = null;
IBaseResource resource = null;
for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) {
for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) {
url = ((IPrimitiveType<String>)nextUrlValue).getValue();
}
}
// Should return 0..1 only
for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) nextValue;
}
retVal.add(Pair.of(url, resource));
}
return retVal;
}
}

View File

@ -42,6 +42,7 @@ import com.google.common.collect.Sets;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -100,25 +101,6 @@ public class XmlParserDstu2Test {
private static final FhirContext ourCtx = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserDstu2Test.class);
/**
* See #312
*/
@Test
public void testEncodeNullExtension() {
Patient patient = new Patient();
patient.getUndeclaredExtensions().add(null); // Purposely add null
patient.getUndeclaredModifierExtensions().add(null); // Purposely add null
patient.getUndeclaredExtensions().add(new ExtensionDt(false, "http://hello.world", new StringDt("Hello World")));
patient.getName().add(null);
patient.addName().getFamily().add(null);
IParser parser = ourCtx.newXmlParser();
String xml = parser.encodeResourceToString(patient);
ourLog.info(xml);
assertEquals("<Patient xmlns=\"http://hl7.org/fhir\"><extension url=\"http://hello.world\"><valueString value=\"Hello World\"/></extension></Patient>", xml);
}
@Test
public void testBundleWithBinary() {
//@formatter:off
@ -256,6 +238,43 @@ public class XmlParserDstu2Test {
assertTrue(parsed.getEntries().get(0).getResource().getId().isEmpty());
}
@Test
public void testEncodeAndParseBundleWithResourceRefs() {
Patient pt = new Patient();
pt.setId("patid");
pt.addName().addFamily("PATIENT");
Organization org = new Organization();
org.setId("orgid");
org.setName("ORG");
pt.getManagingOrganization().setResource(org);
ca.uhn.fhir.model.dstu2.resource.Bundle bundle = new ca.uhn.fhir.model.dstu2.resource.Bundle();
bundle.addEntry().setResource(pt);
bundle.addEntry().setResource(org);
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle);
ourLog.info(encoded);
//@formatter:off
assertThat(encoded, stringContainsInOrder(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<managingOrganization>",
"<reference value=\"Organization/orgid\"/>",
"</managingOrganization>"
));
//@formatter:on
bundle = ourCtx.newXmlParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, encoded);
pt = (Patient) bundle.getEntry().get(0).getResource();
org = (Organization) bundle.getEntry().get(1).getResource();
assertEquals("Organization/orgid", org.getId().getValue());
assertEquals("Organization/orgid", pt.getManagingOrganization().getReference().getValue());
assertSame(org, pt.getManagingOrganization().getResource());
}
@Test
public void testEncodeAndParseContained() {
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -664,6 +683,31 @@ public class XmlParserDstu2Test {
assertEquals("VERSION2", label.getVersion());
}
/**
* https://chat.fhir.org/#narrow/stream/implementers/topic/fullUrl.20and.20MessageHeader.2Eid
*/
@Test
public void testEncodeAndParseUuid() {
Patient p = new Patient();
p.addName().addFamily("FAMILY");
p.setId("urn:uuid:4f08cf3d-9f41-41bb-9e10-6e34c5b8f602");
ca.uhn.fhir.model.dstu2.resource.Bundle b = new ca.uhn.fhir.model.dstu2.resource.Bundle();
Entry be = b.addEntry();
be.setResource(p);
be.getRequest().setUrl(p.getId().getValue());
String output = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(b);
ourLog.info(output);
ca.uhn.fhir.model.dstu2.resource.Bundle parsedBundle = ourCtx.newXmlParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, output);
Entry parsedEntry = parsedBundle.getEntry().get(0);
assertEquals("urn:uuid:4f08cf3d-9f41-41bb-9e10-6e34c5b8f602", parsedEntry.getRequest().getUrl());
assertEquals("urn:uuid:4f08cf3d-9f41-41bb-9e10-6e34c5b8f602", parsedEntry.getResource().getId().getValue());
}
/**
* See #103
*/
@ -1052,6 +1096,25 @@ public class XmlParserDstu2Test {
}
/**
* See #312
*/
@Test
public void testEncodeNullExtension() {
Patient patient = new Patient();
patient.getUndeclaredExtensions().add(null); // Purposely add null
patient.getUndeclaredModifierExtensions().add(null); // Purposely add null
patient.getUndeclaredExtensions().add(new ExtensionDt(false, "http://hello.world", new StringDt("Hello World")));
patient.getName().add(null);
patient.addName().getFamily().add(null);
IParser parser = ourCtx.newXmlParser();
String xml = parser.encodeResourceToString(patient);
ourLog.info(xml);
assertEquals("<Patient xmlns=\"http://hl7.org/fhir\"><extension url=\"http://hello.world\"><valueString value=\"Hello World\"/></extension></Patient>", xml);
}
@Test
public void testEncodeReferenceUsingUnqualifiedResourceWorksCorrectly() {
@ -1089,6 +1152,7 @@ public class XmlParserDstu2Test {
assertThat(str, containsString("<reference value=\"Observation/phitcc_obs_bp_dia\"/>"));
}
@Test
public void testEncodeSummary() {
Patient patient = new Patient();
@ -1107,7 +1171,6 @@ public class XmlParserDstu2Test {
assertThat(encoded, not(containsString("maritalStatus")));
}
@Test
public void testEncodeSummary2() {
Patient patient = new Patient();
@ -1131,54 +1194,6 @@ public class XmlParserDstu2Test {
assertThat(encoded, not(containsString("maritalStatus")));
}
@Test
public void testEncodeWithEncodeElements() throws Exception {
Patient patient = new Patient();
patient.addName().addFamily("FAMILY");
patient.addAddress().addLine("LINE1");
ca.uhn.fhir.model.dstu2.resource.Bundle bundle = new ca.uhn.fhir.model.dstu2.resource.Bundle();
bundle.setTotal(100);
bundle.addEntry().setResource(patient);
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name", "Bundle.entry")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
assertThat(out, not(containsString("total")));
assertThat(out, (containsString("Patient")));
assertThat(out, (containsString("name")));
assertThat(out, not(containsString("address")));
}
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
assertThat(out, (containsString("total")));
assertThat(out, (containsString("Patient")));
assertThat(out, (containsString("name")));
assertThat(out, not(containsString("address")));
}
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
assertThat(out, (containsString("total")));
assertThat(out, (containsString("Patient")));
assertThat(out, (containsString("name")));
assertThat(out, (containsString("address")));
}
}
@Test
public void testEncodeWithDontEncodeElements() throws Exception {
@ -1252,6 +1267,54 @@ public class XmlParserDstu2Test {
}
@Test
public void testEncodeWithEncodeElements() throws Exception {
Patient patient = new Patient();
patient.addName().addFamily("FAMILY");
patient.addAddress().addLine("LINE1");
ca.uhn.fhir.model.dstu2.resource.Bundle bundle = new ca.uhn.fhir.model.dstu2.resource.Bundle();
bundle.setTotal(100);
bundle.addEntry().setResource(patient);
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name", "Bundle.entry")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
assertThat(out, not(containsString("total")));
assertThat(out, (containsString("Patient")));
assertThat(out, (containsString("name")));
assertThat(out, not(containsString("address")));
}
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient.name")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
assertThat(out, (containsString("total")));
assertThat(out, (containsString("Patient")));
assertThat(out, (containsString("name")));
assertThat(out, not(containsString("address")));
}
{
IParser p = ourCtx.newXmlParser();
p.setEncodeElements(new HashSet<String>(Arrays.asList("Patient")));
p.setEncodeElementsAppliesToResourceTypes(new HashSet<String>(Arrays.asList("Patient")));
p.setPrettyPrint(true);
String out = p.encodeResourceToString(bundle);
ourLog.info(out);
assertThat(out, (containsString("total")));
assertThat(out, (containsString("Patient")));
assertThat(out, (containsString("name")));
assertThat(out, (containsString("address")));
}
}
@Test
public void testMoreExtensions() throws Exception {
@ -1353,6 +1416,7 @@ public class XmlParserDstu2Test {
}
@Test
public void testParseAndEncodeBundleNewStyle() throws Exception {
String content = IOUtils.toString(XmlParserDstu2Test.class.getResourceAsStream("/bundle-example.xml"));

View File

@ -199,6 +199,39 @@ public class GenericClientDstu2Test {
}
/**
* See #322
*/
@Test
public void testFetchConformanceWithSmartExtensionsAltCase() throws Exception {
final String respString = IOUtils.toString(GenericClientDstu2Test.class.getResourceAsStream("/conformance_322.json")).replace("valueuri", "valueUri");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:8080/fhir");
Conformance conf = client.fetchConformance().ofType(Conformance.class).execute();
Rest rest = conf.getRest().get(0);
RestSecurity security = rest.getSecurity();
List<ExtensionDt> ext = security.getUndeclaredExtensionsByUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
List<ExtensionDt> tokenExts = ext.get(0).getUndeclaredExtensionsByUrl("token");
ExtensionDt tokenExt = tokenExts.get(0);
UriDt value = (UriDt) tokenExt.getValue();
assertEquals("https://my-server.org/token", value.getValueAsString());
}
@Test
public void testAcceptHeaderPreflightConformance() throws Exception {
String methodName = "testAcceptHeaderPreflightConformance";

View File

@ -102,6 +102,45 @@ public class XmlParserDstu3Test {
ourCtx.setNarrativeGenerator(null);
}
@Test
public void testEncodeAndParseBundleWithResourceRefs() {
Patient pt = new Patient();
pt.setId("patid");
pt.addName().addFamily("PATIENT");
Organization org = new Organization();
org.setId("orgid");
org.setName("ORG");
pt.getManagingOrganization().setResource(org);
Bundle bundle = new Bundle();
bundle.addEntry().setResource(pt);
bundle.addEntry().setResource(org);
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle);
ourLog.info(encoded);
//@formatter:off
assertThat(encoded, stringContainsInOrder(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<managingOrganization>",
"<reference value=\"Organization/orgid\"/>",
"</managingOrganization>"
));
//@formatter:on
bundle = ourCtx.newXmlParser().parseResource(Bundle.class, encoded);
pt = (Patient) bundle.getEntry().get(0).getResource();
org = (Organization) bundle.getEntry().get(1).getResource();
assertEquals("Organization/orgid", org.getIdElement().getValue());
assertEquals("Organization/orgid", pt.getManagingOrganization().getReferenceElement().getValue());
assertSame(org, pt.getManagingOrganization().getResource());
}
@Test
public void testBundleWithBinary() {
//@formatter:off

View File

@ -371,6 +371,17 @@
DateTimeType should fail to parse 1974-12-25+10:00 as this is not
a valid time in FHIR. Thanks to Grahame Grieve for reporting!
</action>
<action type="fix">
When parsing a Bundle resource, if the Bundle.entry.request.url contains a UUID
but the resource body has no ID, the Resource.id will be populated with the ID from the
Bundle.entry.request.url. This is helpful when round tripping Bundles containing
UUIDs.
</action>
<action type="fix">
When parsing a DSTU3 bundle, references between resources did not have
the actual resource instance populated into the reference if the
IDs matched as they did in DSTU1/2.
</action>
</release>
<release version="1.4" date="2016-02-04">
<action type="add">