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.api.annotation.Child;
import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.composite.BaseContainedDt; 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.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.narrative.INarrativeGenerator; 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.ScalarType;
import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType; import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@ -695,7 +693,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
theEventWriter.endArray(); theEventWriter.endArray();
} }
addExtensionMetadata(extensionMetadataKeys, theEventWriter); addExtensionMetadata(theResDef, theResource, theContainedResource, theSubResource, extensionMetadataKeys, resDef, theEventWriter);
theEventWriter.endObject(); // end meta theEventWriter.endObject(); // end meta
} }
@ -706,52 +704,21 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
theEventWriter.endObject(); 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()) { if (extensionMetadataKeys.isEmpty()) {
return; return;
} }
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionKeys = new ArrayList<>(extensionMetadataKeys.size()); ExtensionDt metaResource = new ExtensionDt();
List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> modifierExtensionKeys = new ArrayList<>(extensionKeys.size());
for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) { for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) {
if (!((ExtensionDt) entry.getValue()).isModifier()) { metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue());
extensionKeys.add(entry);
} else {
modifierExtensionKeys.add(entry);
}
} }
encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
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();
} }
/** /**

View File

@ -489,8 +489,6 @@ class ParserState<T> {
} }
} else if ("url".equals(theName) && myInstance instanceof ExtensionDt) { } else if ("url".equals(theName) && myInstance instanceof ExtensionDt) {
((ExtensionDt) myInstance).setUrl(theValue); ((ExtensionDt) myInstance).setUrl(theValue);
} else if ("value".equals(theName) && myInstance instanceof BaseResourceReferenceDt) {
((BaseResourceReferenceDt) myInstance).setReference(new IdDt(theValue));
} else { } else {
if (myJsonMode) { if (myJsonMode) {
myErrorHandler.incorrectJsonType(null, myElementName, ValueType.OBJECT, null, ValueType.SCALAR, ScalarType.STRING); 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.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.*; import java.util.*;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hamcrest.CoreMatchers; import org.apache.commons.lang3.time.DateUtils;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass; import org.junit.AfterClass;
@ -37,7 +35,6 @@ import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.*; import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseCodingDt;
@ -1716,41 +1713,67 @@ public class JsonParserDstu2Test {
public void testEncodeResourceWithExtensionMetadata() throws Exception { public void testEncodeResourceWithExtensionMetadata() throws Exception {
ProcedureRequest procedureRequest = new ProcedureRequest(); ProcedureRequest procedureRequest = new ProcedureRequest();
procedureRequest.setStatus(ProcedureRequestStatusEnum.ACCEPTED); procedureRequest.setStatus(ProcedureRequestStatusEnum.ACCEPTED);
addExtensionResourceMetadataKeyToResource(procedureRequest, false, "http://someurl.com", "SomeValue"); ExtensionDt timestamp = new ExtensionDt(false, "http://fhir.sjanic.com/timestamp");
addExtensionResourceMetadataKeyToResource(procedureRequest, false, "http://someurl2.com", "SomeValue2"); timestamp.addUndeclaredExtension(false, "http://fhir.sjanic.com/timestamp/user", new ResourceReferenceDt("sjanic"));
addExtensionResourceMetadataKeyToResource(procedureRequest, true, "http://someurl.com/modifier", "SomeValue"); timestamp.addUndeclaredExtension(false, "http://fhir.sjanic.com/timestamp/instance", new InstantDt("2012-01-01T13:00:00Z"));
addExtensionResourceMetadataKeyToResource(procedureRequest, true, "http://someurl.com/modifier2", "SomeValue2"); 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")));
Organization organization = new Organization(); ExtensionDt payment = new ExtensionDt(true, "http://fhir.sjanic.com/procedureRequest/requiresPatientPayment", new BooleanDt(true));
organization.setId(new IdDt("Someorganization")); procedureRequest.getResourceMetadata().put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(timestamp.getUrl()), timestamp);
ExtensionDt extensionDt = new ExtensionDt(false, "http://someurl3.com", new ResourceReferenceDt(organization.getId())); procedureRequest.getResourceMetadata().put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(payment.getUrl()), payment);
procedureRequest.getResourceMetadata()
.put(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(extensionDt.getUrl()), extensionDt);
String json = ourCtx.newJsonParser().encodeResourceToString(procedureRequest); String json = ourCtx.newJsonParser().encodeResourceToString(procedureRequest);
// @formatter:off // @formatter:off
assertThat(json, stringContainsInOrder( assertThat(json, stringContainsInOrder(
"\"meta\":{", "\"meta\":{"+
"\"extension\":[{", "\"extension\":["+
"\"url\":\"http://someurl.com\",", "{"+
"\"valueString\":\"SomeValue\"", "\"url\":\"http://fhir.sjanic.com/timestamp\","+
"},{", "\"extension\":["+
"\"url\":\"http://someurl2.com\",", "{"+
"\"valueString\":\"SomeValue2\"", "\"url\":\"http://fhir.sjanic.com/timestamp/user\","+
"}],", "\"valueReference\":{"+
"\"modifierExtension\":[{", "\"reference\":\"sjanic\""+
"\"url\":\"http://someurl.com/modifier\",", "}"+
"\"valueString\":\"SomeValue\"", "},"+
"},{", "{"+
"\"url\":\"http://someurl.com/modifier2\",", "\"url\":\"http://fhir.sjanic.com/timestamp/instance\","+
"\"valueString\":\"SomeValue2\"", "\"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 // @formatter:on
} }
@Test @Test
public void testParseResourceWithReferenceExtensionMetadata() throws Exception { public void testParseResourceWithExtensionMetadata() throws Exception {
String input = IOUtils.toString(getClass().getResourceAsStream("/procedure-request.json")); String input = IOUtils.toString(getClass().getResourceAsStream("/procedure-request.json"));
IParser parser = ourCtx.newJsonParser(); IParser parser = ourCtx.newJsonParser();
IParserErrorHandler peh = mock(IParserErrorHandler.class); IParserErrorHandler peh = mock(IParserErrorHandler.class);
@ -1760,37 +1783,21 @@ public class JsonParserDstu2Test {
ArgumentCaptor<String> capt = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> capt = ArgumentCaptor.forClass(String.class);
verify(peh, Mockito.never()).unknownElement(Mockito.isNull(IParseLocation.class), capt.capture()); 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( private void assertParsedResourcesExtensionMetadata(ProcedureRequest resource) throws Exception {
BaseResource resource, ExtensionDt payment = (ExtensionDt) resource.getResourceMetadata().get(
String url, new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey("http://fhir.sjanic.com/procedureRequest/requiresPatientPayment"));
boolean isModifier, assertThat(payment.isModifier(), equalTo(true));
Class<?> expectedType, assertThat(((BooleanDt)payment.getValue()).getValue(), equalTo(true));
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));
}
@Test(expected = IllegalArgumentException.class) TimestampFields timestampFields = new TimestampFields(resource);
public void testCannotEncodeSubextensionsOnMeta() { assertThat(timestampFields.user.getReference().getIdPart(), equalTo("sjanic"));
ProcedureRequest procedureRequest = new ProcedureRequest(); assertThat(timestampFields.instance.getValue(), equalTo(new InstantDt("2012-01-01T13:00:00Z").getValue()));
procedureRequest.setStatus(ProcedureRequestStatusEnum.ACCEPTED); assertThat(timestampFields.organization.getReference().getIdPart(), equalTo("sjanic_org"));
ExtensionDt parent = new ExtensionDt(false, "#parent"); assertThat(timestampFields.role.getCodingFirstRep().getSystem(), equalTo("sjanic"));
parent.addUndeclaredExtension(new ExtensionDt(false, "#child", new DurationDt().setValue(123))); assertThat(timestampFields.role.getCodingFirstRep().getCode(), equalTo("Doctor"));
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);
} }
@Test @Test
@ -2058,4 +2065,25 @@ public class JsonParserDstu2Test {
Assert.assertEquals(1, extlst.size()); Assert.assertEquals(1, extlst.size());
Assert.assertEquals(refVal, ((ResourceReferenceDt) extlst.get(0).getValue()).getReference().getValue()); 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": { "meta": {
"extension": [ "extension": [
{ {
"url": "http://someurl3.com", "url": "http://fhir.sjanic.com/timestamp",
"valueReference": "Organization/dc0b549a-f852-11e6-bc64-92361f002671" "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
} }
] ]
}, },