Refactored the meta extensions, so it reuses the existing logic that is

used for encoding and parsing extensions on resources.
This commit is contained in:
Simon Janic 2017-11-26 14:22:41 +01:00
parent 46fcceb9c6
commit f426b0679c
4 changed files with 133 additions and 105 deletions

View File

@ -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<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys, JsonLikeWriter theEventWriter) throws IOException {
private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource,
boolean theContainedResource, boolean theSubResource,
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys,
RuntimeResourceDefinition resDef,
JsonLikeWriter theEventWriter) throws IOException {
if (extensionMetadataKeys.isEmpty()) {
return;
}
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionKeys = new ArrayList<>(extensionMetadataKeys.size());
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> modifierExtensionKeys = new ArrayList<>(extensionKeys.size());
ExtensionDt metaResource = new ExtensionDt();
for (Map.Entry<ResourceMetadataKeyEnum<?>, 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<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensions, String arrayName, JsonLikeWriter theEventWriter) throws IOException {
if (extensions.isEmpty()) {
return;
}
beginArray(theEventWriter, arrayName);
for (Map.Entry<ResourceMetadataKeyEnum<?>, 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));
}
/**

View File

@ -489,8 +489,6 @@ class ParserState<T> {
}
} 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);

View File

@ -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<String> 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<String, ExtensionDt> 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();
}
}
}

View File

@ -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
}
]
},