diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index f4b3b3eca45..710c257d54b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -26,7 +26,6 @@ import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseContainedDt; -import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.narrative.INarrativeGenerator; @@ -34,7 +33,6 @@ import ca.uhn.fhir.parser.json.*; import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType; import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.util.BinaryUtil; import ca.uhn.fhir.util.ElementUtil; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -695,7 +693,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.endArray(); } - addExtensionMetadata(extensionMetadataKeys, theEventWriter); + addExtensionMetadata(theResDef, theResource, theContainedResource, theSubResource, extensionMetadataKeys, resDef, theEventWriter); theEventWriter.endObject(); // end meta } @@ -706,52 +704,21 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.endObject(); } - private void addExtensionMetadata(List, Object>> extensionMetadataKeys, JsonLikeWriter theEventWriter) throws IOException { + + private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource, + boolean theContainedResource, boolean theSubResource, + List, Object>> extensionMetadataKeys, + RuntimeResourceDefinition resDef, + JsonLikeWriter theEventWriter) throws IOException { if (extensionMetadataKeys.isEmpty()) { return; } - List, Object>> extensionKeys = new ArrayList<>(extensionMetadataKeys.size()); - List, Object>> modifierExtensionKeys = new ArrayList<>(extensionKeys.size()); + ExtensionDt metaResource = new ExtensionDt(); for (Map.Entry, Object> entry : extensionMetadataKeys) { - if (!((ExtensionDt) entry.getValue()).isModifier()) { - extensionKeys.add(entry); - } else { - modifierExtensionKeys.add(entry); - } + metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue()); } - - writeMetadataExtensions(extensionKeys, "extension", theEventWriter); - writeMetadataExtensions(modifierExtensionKeys, "modifierExtension", theEventWriter); - } - - private void writeMetadataExtensions(List, Object>> extensions, String arrayName, JsonLikeWriter theEventWriter) throws IOException { - if (extensions.isEmpty()) { - return; - } - beginArray(theEventWriter, arrayName); - for (Map.Entry, Object> key : extensions) { - ExtensionDt extension = (ExtensionDt) key.getValue(); - if (!extension.getAllUndeclaredExtensions().isEmpty()) { - throw new IllegalArgumentException("Sub-extensions on metadata isn't supported"); - } - theEventWriter.beginObject(); - writeOptionalTagWithTextNode(theEventWriter, "url", extension.getUrl()); - RuntimeChildUndeclaredExtensionDefinition runtimeDefinitions = myContext.getRuntimeChildUndeclaredExtensionDefinition(); - - String extensionDatatype = runtimeDefinitions.getChildNameByDatatype(extension.getValue().getClass()); - if (extension.getValue() instanceof IPrimitiveDatatype) { - writeOptionalTagWithTextNode(theEventWriter, extensionDatatype, extension.getValueAsPrimitive()); - } else { - if (extension.getValue() instanceof BaseResourceReferenceDt) { - writeOptionalTagWithTextNode(theEventWriter, extensionDatatype, ((BaseResourceReferenceDt) extension.getValue()).getReference()); - } else { - throw new IllegalArgumentException("Cannot parse meta extension with type: " + extension.getValue().getClass().getSimpleName()); - } - } - theEventWriter.endObject(); - } - theEventWriter.endArray(); + encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource)); } /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index 194e026cde3..fab95e21059 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -489,8 +489,6 @@ class ParserState { } } else if ("url".equals(theName) && myInstance instanceof ExtensionDt) { ((ExtensionDt) myInstance).setUrl(theValue); - } else if ("value".equals(theName) && myInstance instanceof BaseResourceReferenceDt) { - ((BaseResourceReferenceDt) myInstance).setReference(new IdDt(theValue)); } else { if (myJsonMode) { myErrorHandler.incorrectJsonType(null, myElementName, ValueType.OBJECT, null, ValueType.SCALAR, ScalarType.STRING); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java index f3f9595a988..136b466aa13 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java @@ -15,16 +15,14 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.io.IOException; import java.io.StringReader; import java.util.*; -import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import org.apache.commons.io.IOUtils; -import org.hamcrest.CoreMatchers; +import org.apache.commons.lang3.time.DateUtils; import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.AfterClass; @@ -37,7 +35,6 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.Sets; -import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.base.composite.BaseCodingDt; @@ -1716,41 +1713,67 @@ public class JsonParserDstu2Test { public void testEncodeResourceWithExtensionMetadata() throws Exception { ProcedureRequest procedureRequest = new ProcedureRequest(); procedureRequest.setStatus(ProcedureRequestStatusEnum.ACCEPTED); - addExtensionResourceMetadataKeyToResource(procedureRequest, false, "http://someurl.com", "SomeValue"); - addExtensionResourceMetadataKeyToResource(procedureRequest, false, "http://someurl2.com", "SomeValue2"); - addExtensionResourceMetadataKeyToResource(procedureRequest, true, "http://someurl.com/modifier", "SomeValue"); - addExtensionResourceMetadataKeyToResource(procedureRequest, true, "http://someurl.com/modifier2", "SomeValue2"); - - Organization organization = new Organization(); - organization.setId(new IdDt("Someorganization")); - ExtensionDt extensionDt = new ExtensionDt(false, "http://someurl3.com", new ResourceReferenceDt(organization.getId())); - procedureRequest.getResourceMetadata() - .put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(extensionDt.getUrl()), extensionDt); + ExtensionDt timestamp = new ExtensionDt(false, "http://fhir.sjanic.com/timestamp"); + timestamp.addUndeclaredExtension(false, "http://fhir.sjanic.com/timestamp/user", new ResourceReferenceDt("sjanic")); + timestamp.addUndeclaredExtension(false, "http://fhir.sjanic.com/timestamp/instance", new InstantDt("2012-01-01T13:00:00Z")); + timestamp.addUndeclaredExtension(false, "http://fhir.sjanic.com/timestamp/organization", new ResourceReferenceDt("sjanic_org")); + timestamp.addUndeclaredExtension(false, "http://fhir.sjanic.com/timestamp/role", new CodeableConceptDt().addCoding(new CodingDt("sjanic", "Doctor").setDisplay("Doctorin"))); + ExtensionDt payment = new ExtensionDt(true, "http://fhir.sjanic.com/procedureRequest/requiresPatientPayment", new BooleanDt(true)); + procedureRequest.getResourceMetadata().put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(timestamp.getUrl()), timestamp); + procedureRequest.getResourceMetadata().put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(payment.getUrl()), payment); String json = ourCtx.newJsonParser().encodeResourceToString(procedureRequest); // @formatter:off assertThat(json, stringContainsInOrder( - "\"meta\":{", - "\"extension\":[{", - "\"url\":\"http://someurl.com\",", - "\"valueString\":\"SomeValue\"", - "},{", - "\"url\":\"http://someurl2.com\",", - "\"valueString\":\"SomeValue2\"", - "}],", - "\"modifierExtension\":[{", - "\"url\":\"http://someurl.com/modifier\",", - "\"valueString\":\"SomeValue\"", - "},{", - "\"url\":\"http://someurl.com/modifier2\",", - "\"valueString\":\"SomeValue2\"", - "}]")); + "\"meta\":{"+ + "\"extension\":["+ + "{"+ + "\"url\":\"http://fhir.sjanic.com/timestamp\","+ + "\"extension\":["+ + "{"+ + "\"url\":\"http://fhir.sjanic.com/timestamp/user\","+ + "\"valueReference\":{"+ + "\"reference\":\"sjanic\""+ + "}"+ + "},"+ + "{"+ + "\"url\":\"http://fhir.sjanic.com/timestamp/instance\","+ + "\"valueInstant\":\"2012-01-01T13:00:00Z\""+ + "},"+ + "{"+ + "\"url\":\"http://fhir.sjanic.com/timestamp/organization\","+ + "\"valueReference\":{"+ + "\"reference\":\"sjanic_org\""+ + "}"+ + "},"+ + "{"+ + "\"url\":\"http://fhir.sjanic.com/timestamp/role\","+ + "\"valueCodeableConcept\":{"+ + "\"coding\":["+ + "{"+ + "\"system\":\"sjanic\","+ + "\"code\":\"Doctor\","+ + "\"display\":\"Doctorin\""+ + "}"+ + "]"+ + "}"+ + "}"+ + "]"+ + "}"+ + "],"+ + "\"modifierExtension\":["+ + "{"+ + "\"url\":\"http://fhir.sjanic.com/procedureRequest/requiresPatientPayment\","+ + "\"valueBoolean\":true"+ + "}"+ + "]"+ + "},")); // @formatter:on } @Test - public void testParseResourceWithReferenceExtensionMetadata() throws Exception { + public void testParseResourceWithExtensionMetadata() throws Exception { String input = IOUtils.toString(getClass().getResourceAsStream("/procedure-request.json")); IParser parser = ourCtx.newJsonParser(); IParserErrorHandler peh = mock(IParserErrorHandler.class); @@ -1760,37 +1783,21 @@ public class JsonParserDstu2Test { ArgumentCaptor capt = ArgumentCaptor.forClass(String.class); verify(peh, Mockito.never()).unknownElement(Mockito.isNull(IParseLocation.class), capt.capture()); - assertReferenceExtensionMetadata(p, "http://someurl3.com", false, ResourceReferenceDt.class, "dc0b549a-f852-11e6-bc64-92361f002671"); + assertParsedResourcesExtensionMetadata(p); } - private void assertReferenceExtensionMetadata( - BaseResource resource, - String url, - boolean isModifier, - Class expectedType, - String expectedValue) { - ExtensionDt extension = (ExtensionDt) resource.getResourceMetadata().get(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(url)); - assertThat(extension.getValue(), CoreMatchers.instanceOf(expectedType)); - assertThat(extension.isModifier(), equalTo(isModifier)); - assertThat(extension.getValue(), CoreMatchers.instanceOf(BaseResourceReferenceDt.class)); - assertThat(((BaseResourceReferenceDt)extension.getValue()).getReference().getIdPart(), equalTo(expectedValue)); - } + private void assertParsedResourcesExtensionMetadata(ProcedureRequest resource) throws Exception { + ExtensionDt payment = (ExtensionDt) resource.getResourceMetadata().get( + new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://fhir.sjanic.com/procedureRequest/requiresPatientPayment")); + assertThat(payment.isModifier(), equalTo(true)); + assertThat(((BooleanDt)payment.getValue()).getValue(), equalTo(true)); - @Test(expected = IllegalArgumentException.class) - public void testCannotEncodeSubextensionsOnMeta() { - ProcedureRequest procedureRequest = new ProcedureRequest(); - procedureRequest.setStatus(ProcedureRequestStatusEnum.ACCEPTED); - ExtensionDt parent = new ExtensionDt(false, "#parent"); - parent.addUndeclaredExtension(new ExtensionDt(false, "#child", new DurationDt().setValue(123))); - procedureRequest.getResourceMetadata().put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(parent.getUrl()), parent); - - String json = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(procedureRequest); - } - - private void addExtensionResourceMetadataKeyToResource(BaseResource resource, boolean isModifier, String url, String value) { - ExtensionDt extensionDt = new ExtensionDt(isModifier, url, new StringDt(value)); - resource.getResourceMetadata() - .put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(extensionDt.getUrl()), extensionDt); + TimestampFields timestampFields = new TimestampFields(resource); + assertThat(timestampFields.user.getReference().getIdPart(), equalTo("sjanic")); + assertThat(timestampFields.instance.getValue(), equalTo(new InstantDt("2012-01-01T13:00:00Z").getValue())); + assertThat(timestampFields.organization.getReference().getIdPart(), equalTo("sjanic_org")); + assertThat(timestampFields.role.getCodingFirstRep().getSystem(), equalTo("sjanic")); + assertThat(timestampFields.role.getCodingFirstRep().getCode(), equalTo("Doctor")); } @Test @@ -2058,4 +2065,25 @@ public class JsonParserDstu2Test { Assert.assertEquals(1, extlst.size()); Assert.assertEquals(refVal, ((ResourceReferenceDt) extlst.get(0).getValue()).getReference().getValue()); } + + private static final class TimestampFields { + ResourceReferenceDt user; + InstantDt instance; + ResourceReferenceDt organization; + CodeableConceptDt role; + + TimestampFields(BaseResource resource) { + ExtensionDt timestamp = (ExtensionDt) resource.getResourceMetadata().get( + new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://fhir.sjanic.com/timestamp")); + + Map timestampFields = new HashMap<>(timestamp.getExtension().size()); + for (ExtensionDt extensionDt : timestamp.getExtension()) { + timestampFields.put(extensionDt.getUrl(), extensionDt); + } + user = ((ResourceReferenceDt)timestampFields.get("http://fhir.sjanic.com/timestamp/user").getValue()); + instance = (InstantDt) timestampFields.get("http://fhir.sjanic.com/timestamp/instance").getValue(); + organization = (ResourceReferenceDt) timestampFields.get("http://fhir.sjanic.com/timestamp/organization").getValue(); + role = (CodeableConceptDt) timestampFields.get("http://fhir.sjanic.com/timestamp/role").getValue(); + } + } } diff --git a/hapi-fhir-structures-dstu2/src/test/resources/procedure-request.json b/hapi-fhir-structures-dstu2/src/test/resources/procedure-request.json index ed498db91aa..3595d3c5908 100644 --- a/hapi-fhir-structures-dstu2/src/test/resources/procedure-request.json +++ b/hapi-fhir-structures-dstu2/src/test/resources/procedure-request.json @@ -3,8 +3,43 @@ "meta": { "extension": [ { - "url": "http://someurl3.com", - "valueReference": "Organization/dc0b549a-f852-11e6-bc64-92361f002671" + "url": "http://fhir.sjanic.com/timestamp", + "extension": [ + { + "url": "http://fhir.sjanic.com/timestamp/user", + "valueReference": { + "reference": "sjanic" + } + }, + { + "url": "http://fhir.sjanic.com/timestamp/instance", + "valueInstant": "2012-01-01T13:00:00Z" + }, + { + "url": "http://fhir.sjanic.com/timestamp/organization", + "valueReference": { + "reference": "sjanic_org" + } + }, + { + "url": "http://fhir.sjanic.com/timestamp/role", + "valueCodeableConcept": { + "coding": [ + { + "system": "sjanic", + "code": "Doctor", + "display": "Doctorin" + } + ] + } + } + ] + } + ], + "modifierExtension": [ + { + "url": "http://fhir.sjanic.com/procedureRequest/requiresPatientPayment", + "valueBoolean": true } ] },