From b4032f4e8ca4d21e1514a8090bc6d582e9614bfe Mon Sep 17 00:00:00 2001 From: Simon Marco Janic Date: Wed, 1 Nov 2017 11:37:51 +0100 Subject: [PATCH] [(master)] Added support for extensions in Meta resource --- .../model/api/ResourceMetadataKeyEnum.java | 25 +++++++ .../java/ca/uhn/fhir/parser/BaseParser.java | 12 ++++ .../java/ca/uhn/fhir/parser/JsonParser.java | 40 ++++++++++- .../java/ca/uhn/fhir/parser/ParserState.java | 19 +++++ .../uhn/fhir/parser/JsonParserDstu2Test.java | 72 +++++++++++++++---- .../src/test/resources/patient1.json | 70 +++++++++--------- 6 files changed, 190 insertions(+), 48 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java index 603ef37151f..b0cb50f692b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java @@ -613,5 +613,30 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { public abstract void put(IAnyResource theResource, T2 theObject); } + + public static final class ExtensionResourceMetadataKey extends ResourceMetadataKeyEnum { + public ExtensionResourceMetadataKey(String url) { + super(url); + } + + @Override + public ExtensionDt get(IResource theResource) { + Object retValObj = theResource.getResourceMetadata().get(this); + if (retValObj == null) { + return null; + } else if (retValObj instanceof ExtensionDt) { + return (ExtensionDt) retValObj; + } + throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + + "' in resource metadata for key " + this.name() + " - Expected " + + ExtensionDt.class.getCanonicalName()); + } + + @Override + public void put(IResource theResource, ExtensionDt theObject) { + theResource.getResourceMetadata().put(this, theObject); + } + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 6e5b8290027..efe0517eac8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -911,6 +911,18 @@ public abstract class BaseParser implements IParser { throw new DataFormatException(nextChild + " has no child of type " + theType); } + protected List, Object>> getExtensionMetadataKeys(IResource resource) { + List, Object>> extensionMetadataKeys = new ArrayList, Object>>(); + for (Map.Entry, Object> entry : resource.getResourceMetadata().entrySet()) { + if (entry.getKey() instanceof ResourceMetadataKeyEnum.ExtensionResourceMetadataKey) { + extensionMetadataKeys.add(entry); + } + } + + return extensionMetadataKeys; + } + + protected static List extractMetadataListNotNull(IResource resource, ResourceMetadataKeyEnum> key) { List securityLabels = key.get(resource); if (securityLabels == null) { 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 f759cefa6cd..02f92d8e9f8 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 @@ -655,8 +655,9 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { if (isBlank(versionIdPart)) { versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource); } + List, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource); - if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) { + if (super.shouldEncodeResourceMeta(resource) && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) || !extensionMetadataKeys.isEmpty()) { beginObject(theEventWriter, "meta"); writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart); writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated); @@ -696,6 +697,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.endArray(); } + addExtensionMetadata(extensionMetadataKeys, theEventWriter); + theEventWriter.endObject(); // end meta } } @@ -705,6 +708,41 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { theEventWriter.endObject(); } + private void addExtensionMetadata(List, Object>> extensionMetadataKeys, JsonLikeWriter theEventWriter) throws IOException { + if (extensionMetadataKeys.isEmpty()) { + return; + } + + List, Object>> extensionKeys = new ArrayList<>(extensionMetadataKeys.size()); + List, Object>> modifierExtensionKeys = new ArrayList<>(extensionKeys.size()); + for (Map.Entry, Object> entry : extensionMetadataKeys) { + if (!((ExtensionDt) entry.getValue()).isModifier()) { + extensionKeys.add(entry); + } else { + modifierExtensionKeys.add(entry); + } + } + + writeMetadataExtensions(extensionKeys, "extension", theEventWriter); + writeMetadataExtensions(extensionKeys, "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(); + theEventWriter.beginObject(); + writeOptionalTagWithTextNode(theEventWriter, "url", extension.getUrl()); + String extensionDatatype = myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildNameByDatatype(extension.getValue().getClass()); + writeOptionalTagWithTextNode(theEventWriter, extensionDatatype, extension.getValueAsPrimitive()); + theEventWriter.endObject(); + } + theEventWriter.endArray(); + } + /** * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object * called _name): resource extensions, and extension extensions 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 e54793602c6..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 @@ -841,6 +841,25 @@ class ParserState { } } + @Override + public void enteringNewElementExtension(StartElement theElem, String theUrlAttr, boolean theIsModifier, final String baseServerUrl) { + ResourceMetadataKeyEnum.ExtensionResourceMetadataKey resourceMetadataKeyEnum = new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(theUrlAttr); + Object metadataValue = myMap.get(resourceMetadataKeyEnum); + ExtensionDt newExtension; + if (metadataValue == null) { + newExtension = new ExtensionDt(theIsModifier); + } else if (metadataValue instanceof ExtensionDt) { + newExtension = (ExtensionDt) metadataValue; + } else { + throw new IllegalStateException("Expected ExtensionDt as custom resource metadata type, got: " + metadataValue.getClass().getSimpleName()); + } + newExtension.setUrl(theUrlAttr); + myMap.put(resourceMetadataKeyEnum, newExtension); + + ExtensionState newState = new ExtensionState(getPreResourceState(), newExtension); + push(newState); + } + } private class MetaVersionElementState extends BaseState { 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 ba0860169a1..dca45aaffa9 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 @@ -1,9 +1,11 @@ package ca.uhn.fhir.parser; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -1688,19 +1690,65 @@ public class JsonParserDstu2Test { Patient p = parser.parseResource(Patient.class, input); ArgumentCaptor capt = ArgumentCaptor.forClass(String.class); - verify(peh, times(4)).unknownElement(Mockito.isNull(IParseLocation.class), capt.capture()); - - //@formatter:off - List strings = capt.getAllValues(); - assertThat(strings, contains( - "extension", - "extension", - "modifierExtension", - "modifierExtension" - )); - //@formatter:off - + verify(peh, Mockito.never()).unknownElement(Mockito.isNull(IParseLocation.class), capt.capture()); assertEquals("Smith", p.getName().get(0).getGiven().get(0).getValue()); + assertExtensionMetadata(p, "fhir-request-method", false, StringDt.class, "POST"); + assertExtensionMetadata(p, "fhir-request-uri", false, UriDt.class, "Patient"); + assertExtensionMetadata(p, "modified-fhir-request-method", true, StringDt.class, "POST"); + assertExtensionMetadata(p, "modified-fhir-request-uri", true, UriDt.class, "Patient"); + } + + private void assertExtensionMetadata( + BaseResource resource, + String url, + boolean isModifier, + Class expectedType, + String expectedValue) { + ExtensionDt extension = (ExtensionDt) resource.getResourceMetadata().get(new ResourceMetadataKeyEnum.ExtensionResourceMetadataKey(url)); + assertThat(extension.getValue(), instanceOf(expectedType)); + assertThat(extension.isModifier(), equalTo(isModifier)); + assertThat(extension.getValueAsPrimitive().getValueAsString(), equalTo(expectedValue)); + } + + @Test + 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"); + + String json = ourCtx.newJsonParser().setPrettyPrint(true).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\",", + "\"valueString\": \"SomeValue\"", + "},", + "{", + "\"url\": \"http://someurl2.com\",", + "\"valueString\": \"SomeValue2\"", + "}", + "]")); + // @formatter:on + } + + 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 diff --git a/hapi-fhir-structures-dstu2/src/test/resources/patient1.json b/hapi-fhir-structures-dstu2/src/test/resources/patient1.json index 6cc128a2684..dbdd0c34abe 100644 --- a/hapi-fhir-structures-dstu2/src/test/resources/patient1.json +++ b/hapi-fhir-structures-dstu2/src/test/resources/patient1.json @@ -1,35 +1,35 @@ -{ - "id": "73b551fb-46f5-4fb8-b735-2399344e9717", - "meta": { - "extension": [ - { - "url": "fhir-request-method", - "valueString": "POST" - }, - { - "url": "fhir-request-uri", - "valueUri": "Patient" - } - ], - "modifierExtension": [ - { - "url": "fhir-request-method", - "valueString": "POST" - }, - { - "url": "fhir-request-uri", - "valueUri": "Patient" - } - ], - "versionId": "01e5253d-d258-494c-8d8e-f22ad6d8f19b", - "lastUpdated": "2016-02-20T11:01:56.155Z" - }, - "name": [ - { - "given": [ - "Smith" - ] - } - ], - "resourceType": "Patient" -} +{ + "id": "73b551fb-46f5-4fb8-b735-2399344e9717", + "meta": { + "extension": [ + { + "url": "fhir-request-method", + "valueString": "POST" + }, + { + "url": "fhir-request-uri", + "valueUri": "Patient" + } + ], + "modifierExtension": [ + { + "url": "modified-fhir-request-method", + "valueString": "POST" + }, + { + "url": "modified-fhir-request-uri", + "valueUri": "Patient" + } + ], + "versionId": "01e5253d-d258-494c-8d8e-f22ad6d8f19b", + "lastUpdated": "2016-02-20T11:01:56.155Z" + }, + "name": [ + { + "given": [ + "Smith" + ] + } + ], + "resourceType": "Patient" +}