001package ca.uhn.fhir.parser;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2019 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 * http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.*;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
025import ca.uhn.fhir.model.api.*;
026import ca.uhn.fhir.model.api.annotation.Child;
027import ca.uhn.fhir.model.base.composite.BaseCodingDt;
028import ca.uhn.fhir.model.base.composite.BaseContainedDt;
029import ca.uhn.fhir.model.primitive.IdDt;
030import ca.uhn.fhir.model.primitive.InstantDt;
031import ca.uhn.fhir.narrative.INarrativeGenerator;
032import ca.uhn.fhir.parser.json.*;
033import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType;
034import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
035import ca.uhn.fhir.rest.api.EncodingEnum;
036import ca.uhn.fhir.util.ElementUtil;
037import com.google.gson.Gson;
038import com.google.gson.GsonBuilder;
039import org.apache.commons.lang3.StringUtils;
040import org.apache.commons.lang3.Validate;
041import org.apache.commons.text.WordUtils;
042import org.hl7.fhir.instance.model.api.*;
043
044import java.io.IOException;
045import java.io.Reader;
046import java.io.Writer;
047import java.math.BigDecimal;
048import java.util.*;
049
050import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE;
051import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE;
052import static org.apache.commons.lang3.StringUtils.*;
053
054/**
055 * This class is the FHIR JSON parser/encoder. Users should not interact with this class directly, but should use
056 * {@link FhirContext#newJsonParser()} to get an instance.
057 */
058public class JsonParser extends BaseParser implements IJsonLikeParser {
059
060        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParser.HeldExtension.class);
061
062        private FhirContext myContext;
063        private boolean myPrettyPrint;
064
065        /**
066         * Do not use this constructor, the recommended way to obtain a new instance of the JSON parser is to invoke
067         * {@link FhirContext#newJsonParser()}.
068         *
069         * @param theParserErrorHandler
070         */
071        public JsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
072                super(theContext, theParserErrorHandler);
073                myContext = theContext;
074        }
075
076        private boolean addToHeldComments(int valueIdx, List<String> theCommentsToAdd, ArrayList<ArrayList<String>> theListToAddTo) {
077                if (theCommentsToAdd.size() > 0) {
078                        theListToAddTo.ensureCapacity(valueIdx);
079                        while (theListToAddTo.size() <= valueIdx) {
080                                theListToAddTo.add(null);
081                        }
082                        if (theListToAddTo.get(valueIdx) == null) {
083                                theListToAddTo.set(valueIdx, new ArrayList<String>());
084                        }
085                        theListToAddTo.get(valueIdx).addAll(theCommentsToAdd);
086                        return true;
087                }
088                return false;
089        }
090
091        private boolean addToHeldExtensions(int valueIdx, List<? extends IBaseExtension<?, ?>> ext, ArrayList<ArrayList<HeldExtension>> list, boolean theIsModifier, CompositeChildElement theChildElem,
092                                                                                                        CompositeChildElement theParent) {
093                if (ext.size() > 0) {
094                        list.ensureCapacity(valueIdx);
095                        while (list.size() <= valueIdx) {
096                                list.add(null);
097                        }
098                        if (list.get(valueIdx) == null) {
099                                list.set(valueIdx, new ArrayList<JsonParser.HeldExtension>());
100                        }
101                        for (IBaseExtension<?, ?> next : ext) {
102                                list.get(valueIdx).add(new HeldExtension(next, theIsModifier, theChildElem, theParent));
103                        }
104                        return true;
105                }
106                return false;
107        }
108
109        private void addToHeldIds(int theValueIdx, ArrayList<String> theListToAddTo, String theId) {
110                theListToAddTo.ensureCapacity(theValueIdx);
111                while (theListToAddTo.size() <= theValueIdx) {
112                        theListToAddTo.add(null);
113                }
114                if (theListToAddTo.get(theValueIdx) == null) {
115                        theListToAddTo.set(theValueIdx, theId);
116                }
117        }
118
119        // private void assertObjectOfType(JsonLikeValue theResourceTypeObj, Object theValueType, String thePosition) {
120        // if (theResourceTypeObj == null) {
121        // throw new DataFormatException("Invalid JSON content detected, missing required element: '" + thePosition + "'");
122        // }
123        //
124        // if (theResourceTypeObj.getValueType() != theValueType) {
125        // throw new DataFormatException("Invalid content of element " + thePosition + ", expected " + theValueType);
126        // }
127        // }
128
129        private void beginArray(JsonLikeWriter theEventWriter, String arrayName) throws IOException {
130                theEventWriter.beginArray(arrayName);
131        }
132
133        private void beginObject(JsonLikeWriter theEventWriter, String arrayName) throws IOException {
134                theEventWriter.beginObject(arrayName);
135        }
136
137        private JsonLikeWriter createJsonWriter(Writer theWriter) {
138                JsonLikeStructure jsonStructure = new GsonStructure();
139                JsonLikeWriter retVal = jsonStructure.getJsonLikeWriter(theWriter);
140                return retVal;
141        }
142
143        public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
144                if (myPrettyPrint) {
145                        theEventWriter.setPrettyPrint(myPrettyPrint);
146                }
147                theEventWriter.init();
148
149                RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
150                encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false,  theEncodeContext);
151                theEventWriter.flush();
152        }
153
154        @Override
155        protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
156                JsonLikeWriter eventWriter = createJsonWriter(theWriter);
157                doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext);
158        }
159
160        @Override
161        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
162                JsonLikeStructure jsonStructure = new GsonStructure();
163                jsonStructure.load(theReader);
164
165                T retVal = doParseResource(theResourceType, jsonStructure);
166
167                return retVal;
168        }
169
170        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, JsonLikeStructure theJsonStructure) {
171                JsonLikeObject object = theJsonStructure.getRootObject();
172
173                JsonLikeValue resourceTypeObj = object.get("resourceType");
174                if (resourceTypeObj == null || !resourceTypeObj.isString() || isBlank(resourceTypeObj.getAsString())) {
175                        throw new DataFormatException("Invalid JSON content detected, missing required element: 'resourceType'");
176                }
177
178                String resourceType = resourceTypeObj.getAsString();
179
180                ParserState<? extends IBaseResource> state = ParserState.getPreResourceInstance(this, theResourceType, myContext, true, getErrorHandler());
181                state.enteringNewElement(null, resourceType);
182
183                parseChildren(object, state);
184
185                state.endingElement();
186                state.endingElement();
187
188                @SuppressWarnings("unchecked")
189                T retVal = (T) state.getObject();
190
191                return retVal;
192        }
193
194        private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue,
195                                                                                                                                 BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, CompositeChildElement theChildElem,
196                                                                                                                                 boolean theForceEmpty, EncodeContext theEncodeContext) throws IOException {
197
198                switch (theChildDef.getChildType()) {
199                        case ID_DATATYPE: {
200                                IIdType value = (IIdType) theNextValue;
201                                String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
202                                if (isBlank(encodedValue)) {
203                                        break;
204                                }
205                                if (theChildName != null) {
206                                        write(theEventWriter, theChildName, encodedValue);
207                                } else {
208                                        theEventWriter.write(encodedValue);
209                                }
210                                break;
211                        }
212                        case PRIMITIVE_DATATYPE: {
213                                final IPrimitiveType<?> value = (IPrimitiveType<?>) theNextValue;
214                                if (isBlank(value.getValueAsString())) {
215                                        if (theForceEmpty) {
216                                                theEventWriter.writeNull();
217                                        }
218                                        break;
219                                }
220
221                                if (value instanceof IBaseIntegerDatatype) {
222                                        if (theChildName != null) {
223                                                write(theEventWriter, theChildName, ((IBaseIntegerDatatype) value).getValue());
224                                        } else {
225                                                theEventWriter.write(((IBaseIntegerDatatype) value).getValue());
226                                        }
227                                } else if (value instanceof IBaseDecimalDatatype) {
228                                        BigDecimal decimalValue = ((IBaseDecimalDatatype) value).getValue();
229                                        decimalValue = new BigDecimal(decimalValue.toString()) {
230                                                private static final long serialVersionUID = 1L;
231
232                                                @Override
233                                                public String toString() {
234                                                        return value.getValueAsString();
235                                                }
236                                        };
237                                        if (theChildName != null) {
238                                                write(theEventWriter, theChildName, decimalValue);
239                                        } else {
240                                                theEventWriter.write(decimalValue);
241                                        }
242                                } else if (value instanceof IBaseBooleanDatatype) {
243                                        if (theChildName != null) {
244                                                write(theEventWriter, theChildName, ((IBaseBooleanDatatype) value).getValue());
245                                        } else {
246                                                Boolean booleanValue = ((IBaseBooleanDatatype) value).getValue();
247                                                if (booleanValue != null) {
248                                                        theEventWriter.write(booleanValue.booleanValue());
249                                                }
250                                        }
251                                } else {
252                                        String valueStr = value.getValueAsString();
253                                        if (theChildName != null) {
254                                                write(theEventWriter, theChildName, valueStr);
255                                        } else {
256                                                theEventWriter.write(valueStr);
257                                        }
258                                }
259                                break;
260                        }
261                        case RESOURCE_BLOCK:
262                        case COMPOSITE_DATATYPE: {
263                                if (theChildName != null) {
264                                        theEventWriter.beginObject(theChildName);
265                                } else {
266                                        theEventWriter.beginObject();
267                                }
268                                encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theChildElem, theEncodeContext);
269                                theEventWriter.endObject();
270                                break;
271                        }
272                        case CONTAINED_RESOURCE_LIST:
273                        case CONTAINED_RESOURCES: {
274                                /*
275                                 * Disabled per #103 ContainedDt value = (ContainedDt) theNextValue; for (IResource next :
276                                 * value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
277                                 * encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true,
278                                 * fixContainedResourceId(next.getId().getValue())); }
279                                 */
280                                List<IBaseResource> containedResources = getContainedResources().getContainedResources();
281                                if (containedResources.size() > 0) {
282                                        beginArray(theEventWriter, theChildName);
283
284                                        for (IBaseResource next : containedResources) {
285                                                IIdType resourceId = getContainedResources().getResourceId(next);
286                                                encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
287                                        }
288
289                                        theEventWriter.endArray();
290                                }
291                                break;
292                        }
293                        case PRIMITIVE_XHTML_HL7ORG:
294                        case PRIMITIVE_XHTML: {
295                                if (!isSuppressNarratives()) {
296                                        IPrimitiveType<?> dt = (IPrimitiveType<?>) theNextValue;
297                                        if (theChildName != null) {
298                                                write(theEventWriter, theChildName, dt.getValueAsString());
299                                        } else {
300                                                theEventWriter.write(dt.getValueAsString());
301                                        }
302                                } else {
303                                        if (theChildName != null) {
304                                                // do nothing
305                                        } else {
306                                                theEventWriter.writeNull();
307                                        }
308                                }
309                                break;
310                        }
311                        case RESOURCE:
312                                IBaseResource resource = (IBaseResource) theNextValue;
313                                RuntimeResourceDefinition def = myContext.getResourceDefinition(resource);
314
315                                theEncodeContext.pushPath(def.getName(), true);
316                                encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, theEncodeContext);
317                                theEncodeContext.popPath();
318
319                                break;
320                        case UNDECL_EXT:
321                        default:
322                                throw new IllegalStateException("Should not have this state here: " + theChildDef.getChildType().name());
323                }
324
325        }
326
327        private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter,
328                                                                                                                                                                 boolean theContainedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException {
329
330                {
331                        String elementId = getCompositeElementId(theElement);
332                        if (isNotBlank(elementId)) {
333                                write(theEventWriter, "id", elementId);
334                        }
335                }
336
337                boolean haveWrittenExtensions = false;
338                for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) {
339
340                        BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
341
342                        if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension")
343                                || nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
344                                if (!haveWrittenExtensions) {
345                                        extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, myContext.getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent, theEncodeContext);
346                                        haveWrittenExtensions = true;
347                                }
348                                continue;
349                        }
350
351                        if (nextChild instanceof RuntimeChildNarrativeDefinition) {
352                                INarrativeGenerator gen = myContext.getNarrativeGenerator();
353                                if (gen != null) {
354                                        INarrative narr;
355                                        if (theResource instanceof IResource) {
356                                                narr = ((IResource) theResource).getText();
357                                        } else if (theResource instanceof IDomainResource) {
358                                                narr = ((IDomainResource) theResource).getText();
359                                        } else {
360                                                narr = null;
361                                        }
362                                        if (narr != null && narr.isEmpty()) {
363                                                gen.populateResourceNarrative(myContext, theResource);
364                                                if (!narr.isEmpty()) {
365                                                        RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
366                                                        String childName = nextChild.getChildNameByDatatype(child.getDatatype());
367                                                        BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
368                                                        encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theContainedResource, nextChildElem, false, theEncodeContext);
369                                                        continue;
370                                                }
371                                        }
372                                }
373                        } else if (nextChild instanceof RuntimeChildContainedResources) {
374                                String childName = nextChild.getValidChildNames().iterator().next();
375                                BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
376                                encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, null, child, childName, theContainedResource, nextChildElem, false, theEncodeContext);
377                                continue;
378                        }
379
380                        List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
381                        values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
382
383                        if (values == null || values.isEmpty()) {
384                                continue;
385                        }
386
387                        String currentChildName = null;
388                        boolean inArray = false;
389
390                        ArrayList<ArrayList<HeldExtension>> extensions = new ArrayList<ArrayList<HeldExtension>>(0);
391                        ArrayList<ArrayList<HeldExtension>> modifierExtensions = new ArrayList<ArrayList<HeldExtension>>(0);
392                        ArrayList<ArrayList<String>> comments = new ArrayList<ArrayList<String>>(0);
393                        ArrayList<String> ids = new ArrayList<String>(0);
394
395                        int valueIdx = 0;
396                        for (IBase nextValue : values) {
397
398                                if (nextValue == null || nextValue.isEmpty()) {
399                                        if (nextValue instanceof BaseContainedDt) {
400                                                if (theContainedResource || getContainedResources().isEmpty()) {
401                                                        continue;
402                                                }
403                                        } else {
404                                                continue;
405                                        }
406                                }
407
408                                BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
409                                if (childNameAndDef == null) {
410                                        continue;
411                                }
412
413                                /*
414                                 * Often the two values below will be the same thing. There are cases though
415                                 * where they will not be. An example would be Observation.value, which is
416                                 * a choice type. If the value contains a Quantity, then:
417                                 * nextChildGenericName = "value"
418                                 * nextChildSpecificName = "valueQuantity"
419                                 */
420                                String nextChildSpecificName = childNameAndDef.getChildName();
421                                String nextChildGenericName = nextChild.getElementName();
422
423                                theEncodeContext.pushPath(nextChildGenericName, false);
424
425                                BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
426                                boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
427
428                                if ((childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && theContainedResource) {
429                                        continue;
430                                }
431
432                                boolean force = false;
433                                if (primitive) {
434                                        if (nextValue instanceof ISupportsUndeclaredExtensions) {
435                                                List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions();
436                                                force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent);
437
438                                                ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions();
439                                                force |= addToHeldExtensions(valueIdx, ext, modifierExtensions, true, nextChildElem, theParent);
440                                        } else {
441                                                if (nextValue instanceof IBaseHasExtensions) {
442                                                        IBaseHasExtensions element = (IBaseHasExtensions) nextValue;
443                                                        List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
444                                                        force |= addToHeldExtensions(valueIdx, ext, extensions, false, nextChildElem, theParent);
445                                                }
446                                                if (nextValue instanceof IBaseHasModifierExtensions) {
447                                                        IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue;
448                                                        List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
449                                                        force |= addToHeldExtensions(valueIdx, ext, extensions, true, nextChildElem, theParent);
450                                                }
451                                        }
452                                        if (nextValue.hasFormatComment()) {
453                                                force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPre(), comments);
454                                                force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPost(), comments);
455                                        }
456                                        String elementId = getCompositeElementId(nextValue);
457                                        if (isNotBlank(elementId)) {
458                                                force = true;
459                                                addToHeldIds(valueIdx, ids, elementId);
460                                        }
461                                }
462
463                                if (currentChildName == null || !currentChildName.equals(nextChildSpecificName)) {
464                                        if (inArray) {
465                                                theEventWriter.endArray();
466                                        }
467                                        if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
468                                                beginArray(theEventWriter, nextChildSpecificName);
469                                                inArray = true;
470                                                encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
471                                        } else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
472                                                // suppress narratives from contained resources
473                                        } else {
474                                                encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, nextChildSpecificName, theContainedResource, nextChildElem, false, theEncodeContext);
475                                        }
476                                        currentChildName = nextChildSpecificName;
477                                } else {
478                                        encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
479                                }
480
481                                valueIdx++;
482                                theEncodeContext.popPath();
483                        }
484
485                        if (inArray) {
486                                theEventWriter.endArray();
487                        }
488
489                        if (!extensions.isEmpty() || !modifierExtensions.isEmpty() || !comments.isEmpty()) {
490                                if (inArray) {
491                                        // If this is a repeatable field, the extensions go in an array too
492                                        beginArray(theEventWriter, '_' + currentChildName);
493                                } else {
494                                        beginObject(theEventWriter, '_' + currentChildName);
495                                }
496
497                                for (int i = 0; i < valueIdx; i++) {
498                                        boolean haveContent = false;
499
500                                        List<HeldExtension> heldExts = Collections.emptyList();
501                                        List<HeldExtension> heldModExts = Collections.emptyList();
502                                        if (extensions.size() > i && extensions.get(i) != null && extensions.get(i).isEmpty() == false) {
503                                                haveContent = true;
504                                                heldExts = extensions.get(i);
505                                        }
506
507                                        if (modifierExtensions.size() > i && modifierExtensions.get(i) != null && modifierExtensions.get(i).isEmpty() == false) {
508                                                haveContent = true;
509                                                heldModExts = modifierExtensions.get(i);
510                                        }
511
512                                        ArrayList<String> nextComments;
513                                        if (comments.size() > i) {
514                                                nextComments = comments.get(i);
515                                        } else {
516                                                nextComments = null;
517                                        }
518                                        if (nextComments != null && nextComments.isEmpty() == false) {
519                                                haveContent = true;
520                                        }
521
522                                        String elementId = null;
523                                        if (ids.size() > i) {
524                                                elementId = ids.get(i);
525                                                haveContent |= isNotBlank(elementId);
526                                        }
527
528                                        if (!haveContent) {
529                                                theEventWriter.writeNull();
530                                        } else {
531                                                if (inArray) {
532                                                        theEventWriter.beginObject();
533                                                }
534                                                if (isNotBlank(elementId)) {
535                                                        write(theEventWriter, "id", elementId);
536                                                }
537                                                if (nextComments != null && !nextComments.isEmpty()) {
538                                                        beginArray(theEventWriter, "fhir_comments");
539                                                        for (String next : nextComments) {
540                                                                theEventWriter.write(next);
541                                                        }
542                                                        theEventWriter.endArray();
543                                                }
544                                                writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, heldExts, heldModExts, theEncodeContext);
545                                                if (inArray) {
546                                                        theEventWriter.endObject();
547                                                }
548                                        }
549                                }
550
551                                if (inArray) {
552                                        theEventWriter.endArray();
553                                } else {
554                                        theEventWriter.endObject();
555                                }
556                        }
557                }
558        }
559
560        private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource,                                                                                                                                        CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException, DataFormatException {
561
562                writeCommentsPreAndPost(theNextValue, theEventWriter);
563                encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theParent, theEncodeContext);
564        }
565
566        @Override
567        public void encodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException {
568                Validate.notNull(theResource, "theResource can not be null");
569                Validate.notNull(theJsonLikeWriter, "theJsonLikeWriter can not be null");
570
571                if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
572                        throw new IllegalArgumentException(
573                                "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
574                }
575
576                EncodeContext encodeContext = new EncodeContext();
577                String resourceName = myContext.getResourceDefinition(theResource).getName();
578                encodeContext.pushPath(resourceName, true);
579                doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
580        }
581
582        private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
583                                                                                                                                 boolean theContainedResource, EncodeContext theEncodeContext) throws IOException {
584                IIdType resourceId = null;
585
586                if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
587                        resourceId = theResource.getIdElement();
588                        if (theResource.getIdElement().getValue().startsWith("urn:")) {
589                                resourceId = null;
590                        }
591                }
592
593                if (!theContainedResource) {
594                        if (!super.shouldEncodeResourceId(theResource, theEncodeContext)) {
595                                resourceId = null;
596                        } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
597                                resourceId = getEncodeForceResourceId();
598                        }
599                }
600
601                encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theContainedResource, resourceId, theEncodeContext);
602        }
603
604        private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
605                                                                                                                                 boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws IOException {
606
607                if (!super.shouldEncodeResource(theResDef.getName())) {
608                        return;
609                }
610
611                if (!theContainedResource) {
612                        super.containResourcesForEncoding(theResource);
613                }
614
615                RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
616
617                if (theObjectNameOrNull == null) {
618                        theEventWriter.beginObject();
619                } else {
620                        beginObject(theEventWriter, theObjectNameOrNull);
621                }
622
623                write(theEventWriter, "resourceType", resDef.getName());
624                if (theResourceId != null && theResourceId.hasIdPart()) {
625                        write(theEventWriter, "id", theResourceId.getIdPart());
626                        final List<HeldExtension> extensions = new ArrayList<>(0);
627                        final List<HeldExtension> modifierExtensions = new ArrayList<>(0);
628                        // Undeclared extensions
629                        extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null);
630                        boolean haveExtension = false;
631                        if (!extensions.isEmpty()) {
632                                haveExtension = true;
633                        }
634
635                        if (theResourceId.hasFormatComment() || haveExtension) {
636                                beginObject(theEventWriter, "_id");
637                                if (theResourceId.hasFormatComment()) {
638                                        writeCommentsPreAndPost(theResourceId, theEventWriter);
639                                }
640                                if (haveExtension) {
641                                        writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
642                                }
643                                theEventWriter.endObject();
644                        }
645                }
646
647                if (theResource instanceof IResource) {
648                        IResource resource = (IResource) theResource;
649                        // Object securityLabelRawObj =
650
651                        List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
652                        List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
653                        profiles = super.getProfileTagsForEncoding(resource, profiles);
654
655                        TagList tags = getMetaTagsForEncoding(resource, theEncodeContext);
656                        InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
657                        IdDt resourceId = resource.getId();
658                        String versionIdPart = resourceId.getVersionIdPart();
659                        if (isBlank(versionIdPart)) {
660                                versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
661                        }
662                        List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource);
663
664                        if (super.shouldEncodeResourceMeta(resource) && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) || !extensionMetadataKeys.isEmpty()) {
665                                beginObject(theEventWriter, "meta");
666
667                                if (shouldEncodePath(resource, "meta.versionId")) {
668                                        writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart);
669                                }
670                                if (shouldEncodePath(resource, "meta.lastUpdated")) {
671                                        writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated);
672                                }
673
674                                if (profiles != null && profiles.isEmpty() == false) {
675                                        beginArray(theEventWriter, "profile");
676                                        for (IIdType profile : profiles) {
677                                                if (profile != null && isNotBlank(profile.getValue())) {
678                                                        theEventWriter.write(profile.getValue());
679                                                }
680                                        }
681                                        theEventWriter.endArray();
682                                }
683
684                                if (securityLabels.isEmpty() == false) {
685                                        beginArray(theEventWriter, "security");
686                                        for (BaseCodingDt securityLabel : securityLabels) {
687                                                theEventWriter.beginObject();
688                                                theEncodeContext.pushPath("security", false);
689                                                encodeCompositeElementChildrenToStreamWriter(resDef, resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
690                                                theEncodeContext.popPath();
691                                                theEventWriter.endObject();
692                                        }
693                                        theEventWriter.endArray();
694                                }
695
696                                if (tags != null && tags.isEmpty() == false) {
697                                        beginArray(theEventWriter, "tag");
698                                        for (Tag tag : tags) {
699                                                if (tag.isEmpty()) {
700                                                        continue;
701                                                }
702                                                theEventWriter.beginObject();
703                                                writeOptionalTagWithTextNode(theEventWriter, "system", tag.getScheme());
704                                                writeOptionalTagWithTextNode(theEventWriter, "code", tag.getTerm());
705                                                writeOptionalTagWithTextNode(theEventWriter, "display", tag.getLabel());
706                                                theEventWriter.endObject();
707                                        }
708                                        theEventWriter.endArray();
709                                }
710
711                                addExtensionMetadata(theResDef, theResource, theContainedResource, extensionMetadataKeys, resDef, theEventWriter, theEncodeContext);
712
713                                theEventWriter.endObject(); // end meta
714                        }
715                }
716
717                encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
718
719                theEventWriter.endObject();
720        }
721
722
723        private void addExtensionMetadata(RuntimeResourceDefinition theResDef, IBaseResource theResource,
724                                                                                                 boolean theContainedResource,
725                                                                                                 List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys,
726                                                                                                 RuntimeResourceDefinition resDef,
727                                                                                                 JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
728                if (extensionMetadataKeys.isEmpty()) {
729                        return;
730                }
731
732                ExtensionDt metaResource = new ExtensionDt();
733                for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) {
734                        metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue());
735                }
736                encodeCompositeElementToStreamWriter(theResDef, theResource, metaResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef, theEncodeContext), theEncodeContext);
737        }
738
739        /**
740         * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object
741         * called _name): resource extensions, and extension extensions
742         */
743        private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef,
744                                                                                                                                                 IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent, EncodeContext theEncodeContext) throws IOException {
745                List<HeldExtension> extensions = new ArrayList<>(0);
746                List<HeldExtension> modifierExtensions = new ArrayList<>(0);
747
748                // Undeclared extensions
749                extractUndeclaredExtensions(theElement, extensions, modifierExtensions, theChildElem, theParent);
750
751                // Declared extensions
752                if (theElementDef != null) {
753                        extractDeclaredExtensions(theElement, theElementDef, extensions, modifierExtensions, theChildElem);
754                }
755
756                // Write the extensions
757                writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
758        }
759
760        private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions,
761                                                                                                                CompositeChildElement theChildElem) {
762                for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) {
763                        for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
764                                if (nextValue != null) {
765                                        if (nextValue.isEmpty()) {
766                                                continue;
767                                        }
768                                        extensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
769                                }
770                        }
771                }
772                for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) {
773                        for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
774                                if (nextValue != null) {
775                                        if (nextValue.isEmpty()) {
776                                                continue;
777                                        }
778                                        modifierExtensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
779                                }
780                        }
781                }
782        }
783
784        private void extractUndeclaredExtensions(IBase theElement, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, CompositeChildElement theChildElem,
785                                                                                                                  CompositeChildElement theParent) {
786                if (theElement instanceof ISupportsUndeclaredExtensions) {
787                        ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement;
788                        List<ExtensionDt> ext = element.getUndeclaredExtensions();
789                        for (ExtensionDt next : ext) {
790                                if (next == null || next.isEmpty()) {
791                                        continue;
792                                }
793                                extensions.add(new HeldExtension(next, false, theChildElem, theParent));
794                        }
795
796                        ext = element.getUndeclaredModifierExtensions();
797                        for (ExtensionDt next : ext) {
798                                if (next == null || next.isEmpty()) {
799                                        continue;
800                                }
801                                modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent));
802                        }
803                } else {
804                        if (theElement instanceof IBaseHasExtensions) {
805                                IBaseHasExtensions element = (IBaseHasExtensions) theElement;
806                                List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
807                                for (IBaseExtension<?, ?> next : ext) {
808                                        if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
809                                                continue;
810                                        }
811                                        extensions.add(new HeldExtension(next, false, theChildElem, theParent));
812                                }
813                        }
814                        if (theElement instanceof IBaseHasModifierExtensions) {
815                                IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) theElement;
816                                List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
817                                for (IBaseExtension<?, ?> next : ext) {
818                                        if (next == null || next.isEmpty()) {
819                                                continue;
820                                        }
821                                        modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent));
822                                }
823                        }
824                }
825        }
826
827        @Override
828        public EncodingEnum getEncoding() {
829                return EncodingEnum.JSON;
830        }
831
832        private JsonLikeArray grabJsonArray(JsonLikeObject theObject, String nextName, String thePosition) {
833                JsonLikeValue object = theObject.get(nextName);
834                if (object == null || object.isNull()) {
835                        return null;
836                }
837                if (!object.isArray()) {
838                        throw new DataFormatException("Syntax error parsing JSON FHIR structure: Expected ARRAY at element '" + thePosition + "', found '" + object.getJsonType() + "'");
839                }
840                return object.getAsArray();
841        }
842
843        // private JsonObject parse(Reader theReader) {
844        //
845        // PushbackReader pbr = new PushbackReader(theReader);
846        // JsonObject object;
847        // try {
848        // while(true) {
849        // int nextInt;
850        // nextInt = pbr.read();
851        // if (nextInt == -1) {
852        // throw new DataFormatException("Did not find any content to parse");
853        // }
854        // if (nextInt == '{') {
855        // pbr.unread('{');
856        // break;
857        // }
858        // if (Character.isWhitespace(nextInt)) {
859        // continue;
860        // }
861        // throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')");
862        // }
863        //
864        // Gson gson = newGson();
865        //
866        // object = gson.fromJson(pbr, JsonObject.class);
867        // } catch (Exception e) {
868        // throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e);
869        // }
870        //
871        // return object;
872        // }
873
874        private void parseAlternates(JsonLikeValue theAlternateVal, ParserState<?> theState, String theElementName, String theAlternateName) {
875                if (theAlternateVal == null || theAlternateVal.isNull()) {
876                        return;
877                }
878
879                if (theAlternateVal.isArray()) {
880                        JsonLikeArray array = theAlternateVal.getAsArray();
881                        if (array.size() > 1) {
882                                throw new DataFormatException("Unexpected array of length " + array.size() + " (expected 0 or 1) for element: " + theElementName);
883                        }
884                        if (array.size() == 0) {
885                                return;
886                        }
887                        parseAlternates(array.get(0), theState, theElementName, theAlternateName);
888                        return;
889                }
890
891                JsonLikeValue alternateVal = theAlternateVal;
892                if (alternateVal.isObject() == false) {
893                        getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.OBJECT, null, alternateVal.getJsonType(), null);
894                        return;
895                }
896
897                JsonLikeObject alternate = alternateVal.getAsObject();
898                for (String nextKey : alternate.keySet()) {
899                        JsonLikeValue nextVal = alternate.get(nextKey);
900                        if ("extension".equals(nextKey)) {
901                                boolean isModifier = false;
902                                JsonLikeArray array = nextVal.getAsArray();
903                                parseExtension(theState, array, isModifier);
904                        } else if ("modifierExtension".equals(nextKey)) {
905                                boolean isModifier = true;
906                                JsonLikeArray array = nextVal.getAsArray();
907                                parseExtension(theState, array, isModifier);
908                        } else if ("id".equals(nextKey)) {
909                                if (nextVal.isString()) {
910                                        theState.attributeValue("id", nextVal.getAsString());
911                                } else {
912                                        getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, nextVal.getJsonType(), nextVal.getDataType());
913                                }
914                        } else if ("fhir_comments".equals(nextKey)) {
915                                parseFhirComments(nextVal, theState);
916                        }
917                }
918        }
919
920        private void parseChildren(JsonLikeObject theObject, ParserState<?> theState) {
921                Set<String> keySet = theObject.keySet();
922
923                int allUnderscoreNames = 0;
924                int handledUnderscoreNames = 0;
925
926                for (String nextName : keySet) {
927                        if ("resourceType".equals(nextName)) {
928                                continue;
929                        } else if ("extension".equals(nextName)) {
930                                JsonLikeArray array = grabJsonArray(theObject, nextName, "extension");
931                                parseExtension(theState, array, false);
932                                continue;
933                        } else if ("modifierExtension".equals(nextName)) {
934                                JsonLikeArray array = grabJsonArray(theObject, nextName, "modifierExtension");
935                                parseExtension(theState, array, true);
936                                continue;
937                        } else if (nextName.equals("fhir_comments")) {
938                                parseFhirComments(theObject.get(nextName), theState);
939                                continue;
940                        } else if (nextName.charAt(0) == '_') {
941                                allUnderscoreNames++;
942                                continue;
943                        }
944
945                        JsonLikeValue nextVal = theObject.get(nextName);
946                        String alternateName = '_' + nextName;
947                        JsonLikeValue alternateVal = theObject.get(alternateName);
948                        if (alternateVal != null) {
949                                handledUnderscoreNames++;
950                        }
951
952                        parseChildren(theState, nextName, nextVal, alternateVal, alternateName, false);
953
954                }
955
956                // if (elementId != null) {
957                // IBase object = (IBase) theState.getObject();
958                // if (object instanceof IIdentifiableElement) {
959                // ((IIdentifiableElement) object).setElementSpecificId(elementId);
960                // } else if (object instanceof IBaseResource) {
961                // ((IBaseResource) object).getIdElement().setValue(elementId);
962                // }
963                // }
964
965                /*
966                 * This happens if an element has an extension but no actual value. I.e.
967                 * if a resource has a "_status" element but no corresponding "status"
968                 * element. This could be used to handle a null value with an extension
969                 * for example.
970                 */
971                if (allUnderscoreNames > handledUnderscoreNames) {
972                        for (String alternateName : keySet) {
973                                if (alternateName.startsWith("_") && alternateName.length() > 1) {
974                                        JsonLikeValue nextValue = theObject.get(alternateName);
975                                        if (nextValue != null) {
976                                                if (nextValue.isObject()) {
977                                                        String nextName = alternateName.substring(1);
978                                                        if (theObject.get(nextName) == null) {
979                                                                theState.enteringNewElement(null, nextName);
980                                                                parseAlternates(nextValue, theState, alternateName, alternateName);
981                                                                theState.endingElement();
982                                                        }
983                                                } else {
984                                                        getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
985                                                }
986                                        }
987                                }
988                        }
989                }
990
991        }
992
993        private void parseChildren(ParserState<?> theState, String theName, JsonLikeValue theJsonVal, JsonLikeValue theAlternateVal, String theAlternateName, boolean theInArray) {
994                if (theName.equals("id")) {
995                        if (!theJsonVal.isString()) {
996                                getErrorHandler().incorrectJsonType(null, "id", ValueType.SCALAR, ScalarType.STRING, theJsonVal.getJsonType(), theJsonVal.getDataType());
997                        }
998                }
999
1000                if (theJsonVal.isArray()) {
1001                        JsonLikeArray nextArray = theJsonVal.getAsArray();
1002
1003                        JsonLikeValue alternateVal = theAlternateVal;
1004                        if (alternateVal != null && alternateVal.isArray() == false) {
1005                                getErrorHandler().incorrectJsonType(null, theAlternateName, ValueType.ARRAY, null, alternateVal.getJsonType(), null);
1006                                alternateVal = null;
1007                        }
1008
1009                        JsonLikeArray nextAlternateArray = JsonLikeValue.asArray(alternateVal); // could be null
1010                        for (int i = 0; i < nextArray.size(); i++) {
1011                                JsonLikeValue nextObject = nextArray.get(i);
1012                                JsonLikeValue nextAlternate = null;
1013                                if (nextAlternateArray != null && nextAlternateArray.size() >= (i + 1)) {
1014                                        nextAlternate = nextAlternateArray.get(i);
1015                                }
1016                                parseChildren(theState, theName, nextObject, nextAlternate, theAlternateName, true);
1017                        }
1018                } else if (theJsonVal.isObject()) {
1019                        if (!theInArray && theState.elementIsRepeating(theName)) {
1020                                getErrorHandler().incorrectJsonType(null, theName, ValueType.ARRAY, null, ValueType.OBJECT, null);
1021                        }
1022
1023                        theState.enteringNewElement(null, theName);
1024                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1025                        JsonLikeObject nextObject = theJsonVal.getAsObject();
1026                        boolean preResource = false;
1027                        if (theState.isPreResource()) {
1028                                JsonLikeValue resType = nextObject.get("resourceType");
1029                                if (resType == null || !resType.isString()) {
1030                                        throw new DataFormatException("Missing required element 'resourceType' from JSON resource object, unable to parse");
1031                                }
1032                                theState.enteringNewElement(null, resType.getAsString());
1033                                preResource = true;
1034                        }
1035                        parseChildren(nextObject, theState);
1036                        if (preResource) {
1037                                theState.endingElement();
1038                        }
1039                        theState.endingElement();
1040                } else if (theJsonVal.isNull()) {
1041                        theState.enteringNewElement(null, theName);
1042                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1043                        theState.endingElement();
1044                } else {
1045                        // must be a SCALAR
1046                        theState.enteringNewElement(null, theName);
1047                        theState.attributeValue("value", theJsonVal.getAsString());
1048                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1049                        theState.endingElement();
1050                }
1051        }
1052
1053        private void parseExtension(ParserState<?> theState, JsonLikeArray theValues, boolean theIsModifier) {
1054                int allUnderscoreNames = 0;
1055                int handledUnderscoreNames = 0;
1056
1057                for (int i = 0; i < theValues.size(); i++) {
1058                        JsonLikeObject nextExtObj = JsonLikeValue.asObject(theValues.get(i));
1059                        JsonLikeValue jsonElement = nextExtObj.get("url");
1060                        String url;
1061                        if (null == jsonElement || !(jsonElement.isScalar())) {
1062                                String parentElementName;
1063                                if (theIsModifier) {
1064                                        parentElementName = "modifierExtension";
1065                                } else {
1066                                        parentElementName = "extension";
1067                                }
1068                                getErrorHandler().missingRequiredElement(new ParseLocation().setParentElementName(parentElementName), "url");
1069                                url = null;
1070                        } else {
1071                                url = getExtensionUrl(jsonElement.getAsString());
1072                        }
1073                        theState.enteringNewElementExtension(null, url, theIsModifier, getServerBaseUrl());
1074                        for (String next : nextExtObj.keySet()) {
1075                                if ("url".equals(next)) {
1076                                        continue;
1077                                } else if ("extension".equals(next)) {
1078                                        JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
1079                                        parseExtension(theState, jsonVal, false);
1080                                } else if ("modifierExtension".equals(next)) {
1081                                        JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
1082                                        parseExtension(theState, jsonVal, true);
1083                                } else if (next.charAt(0) == '_') {
1084                                        allUnderscoreNames++;
1085                                        continue;
1086                                } else {
1087                                        JsonLikeValue jsonVal = nextExtObj.get(next);
1088                                        String alternateName = '_' + next;
1089                                        JsonLikeValue alternateVal = nextExtObj.get(alternateName);
1090                                        if (alternateVal != null) {
1091                                                handledUnderscoreNames++;
1092                                        }
1093                                        parseChildren(theState, next, jsonVal, alternateVal, alternateName, false);
1094                                }
1095                        }
1096
1097                        /*
1098                         * This happens if an element has an extension but no actual value. I.e.
1099                         * if a resource has a "_status" element but no corresponding "status"
1100                         * element. This could be used to handle a null value with an extension
1101                         * for example.
1102                         */
1103                        if (allUnderscoreNames > handledUnderscoreNames) {
1104                                for (String alternateName : nextExtObj.keySet()) {
1105                                        if (alternateName.startsWith("_") && alternateName.length() > 1) {
1106                                                JsonLikeValue nextValue = nextExtObj.get(alternateName);
1107                                                if (nextValue != null) {
1108                                                        if (nextValue.isObject()) {
1109                                                                String nextName = alternateName.substring(1);
1110                                                                if (nextExtObj.get(nextName) == null) {
1111                                                                        theState.enteringNewElement(null, nextName);
1112                                                                        parseAlternates(nextValue, theState, alternateName, alternateName);
1113                                                                        theState.endingElement();
1114                                                                }
1115                                                        } else {
1116                                                                getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
1117                                                        }
1118                                                }
1119                                        }
1120                                }
1121                        }
1122                        theState.endingElement();
1123                }
1124        }
1125
1126        private void parseFhirComments(JsonLikeValue theObject, ParserState<?> theState) {
1127                if (theObject.isArray()) {
1128                        JsonLikeArray comments = theObject.getAsArray();
1129                        for (int i = 0; i < comments.size(); i++) {
1130                                JsonLikeValue nextComment = comments.get(i);
1131                                if (nextComment.isString()) {
1132                                        String commentText = nextComment.getAsString();
1133                                        if (commentText != null) {
1134                                                theState.commentPre(commentText);
1135                                        }
1136                                }
1137                        }
1138                }
1139        }
1140
1141        @Override
1142        public <T extends IBaseResource> T parseResource(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure) throws DataFormatException {
1143
1144                /*****************************************************
1145                 * ************************************************* *
1146                 * ** NOTE: this duplicates most of the code in ** *
1147                 * ** BaseParser.parseResource(Class<T>, Reader). ** *
1148                 * ** Unfortunately, there is no way to avoid ** *
1149                 * ** this without doing some refactoring of the ** *
1150                 * ** BaseParser class. ** *
1151                 * ************************************************* *
1152                 *****************************************************/
1153
1154                /*
1155                 * We do this so that the context can verify that the structure is for
1156                 * the correct FHIR version
1157                 */
1158                if (theResourceType != null) {
1159                        myContext.getResourceDefinition(theResourceType);
1160                }
1161
1162                // Actually do the parse
1163                T retVal = doParseResource(theResourceType, theJsonLikeStructure);
1164
1165                RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal);
1166                if ("Bundle".equals(def.getName())) {
1167
1168                        BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
1169                        BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
1170                        List<IBase> entries = entryChild.getAccessor().getValues(retVal);
1171                        if (entries != null) {
1172                                for (IBase nextEntry : entries) {
1173
1174                                        /**
1175                                         * If Bundle.entry.fullUrl is populated, set the resource ID to that
1176                                         */
1177                                        // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match the
1178                                        // fullUrl idPart
1179                                        BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
1180                                        if (fullUrlChild == null) {
1181                                                continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
1182                                        }
1183                                        List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
1184                                        if (fullUrl != null && !fullUrl.isEmpty()) {
1185                                                IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
1186                                                if (value.isEmpty() == false) {
1187                                                        List<IBase> entryResources = entryDef.getChildByName("resource").getAccessor().getValues(nextEntry);
1188                                                        if (entryResources != null && entryResources.size() > 0) {
1189                                                                IBaseResource res = (IBaseResource) entryResources.get(0);
1190                                                                String versionId = res.getIdElement().getVersionIdPart();
1191                                                                res.setId(value.getValueAsString());
1192                                                                if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
1193                                                                        res.setId(res.getIdElement().withVersion(versionId));
1194                                                                }
1195                                                        }
1196                                                }
1197                                        }
1198
1199                                }
1200                        }
1201
1202                }
1203
1204                return retVal;
1205        }
1206
1207        @Override
1208        public IBaseResource parseResource(JsonLikeStructure theJsonLikeStructure) throws DataFormatException {
1209                return parseResource(null, theJsonLikeStructure);
1210        }
1211
1212        @Override
1213        public IParser setPrettyPrint(boolean thePrettyPrint) {
1214                myPrettyPrint = thePrettyPrint;
1215                return this;
1216        }
1217
1218        private void write(JsonLikeWriter theEventWriter, String theChildName, Boolean theValue) throws IOException {
1219                if (theValue != null) {
1220                        theEventWriter.write(theChildName, theValue.booleanValue());
1221                }
1222        }
1223
1224        // private void parseExtensionInDstu2Style(boolean theModifier, ParserState<?> theState, String
1225        // theParentExtensionUrl, String theExtensionUrl, JsonArray theValues) {
1226        // String extUrl = UrlUtil.constructAbsoluteUrl(theParentExtensionUrl, theExtensionUrl);
1227        // theState.enteringNewElementExtension(null, extUrl, theModifier);
1228        //
1229        // for (int extIdx = 0; extIdx < theValues.size(); extIdx++) {
1230        // JsonObject nextExt = theValues.getJsonObject(extIdx);
1231        // for (String nextKey : nextExt.keySet()) {
1232        // // if (nextKey.startsWith("value") && nextKey.length() > 5 &&
1233        // // myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(nextKey) != null) {
1234        // JsonElement jsonVal = nextExt.get(nextKey);
1235        // if (jsonVal.getValueType() == ValueType.ARRAY) {
1236        // /*
1237        // * Extension children which are arrays are sub-extensions. Any other value type should be treated as a value.
1238        // */
1239        // JsonArray arrayValue = (JsonArray) jsonVal;
1240        // parseExtensionInDstu2Style(theModifier, theState, extUrl, nextKey, arrayValue);
1241        // } else {
1242        // parseChildren(theState, nextKey, jsonVal, null, null);
1243        // }
1244        // }
1245        // }
1246        //
1247        // theState.endingElement();
1248        // }
1249
1250        private void write(JsonLikeWriter theEventWriter, String theChildName, BigDecimal theDecimalValue) throws IOException {
1251                theEventWriter.write(theChildName, theDecimalValue);
1252        }
1253
1254        private void write(JsonLikeWriter theEventWriter, String theChildName, Integer theValue) throws IOException {
1255                theEventWriter.write(theChildName, theValue);
1256        }
1257
1258        private void writeCommentsPreAndPost(IBase theNextValue, JsonLikeWriter theEventWriter) throws IOException {
1259                if (theNextValue.hasFormatComment()) {
1260                        beginArray(theEventWriter, "fhir_comments");
1261                        List<String> pre = theNextValue.getFormatCommentsPre();
1262                        if (pre.isEmpty() == false) {
1263                                for (String next : pre) {
1264                                        theEventWriter.write(next);
1265                                }
1266                        }
1267                        List<String> post = theNextValue.getFormatCommentsPost();
1268                        if (post.isEmpty() == false) {
1269                                for (String next : post) {
1270                                        theEventWriter.write(next);
1271                                }
1272                        }
1273                        theEventWriter.endArray();
1274                }
1275        }
1276
1277        private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
1278                                                                                                                        List<HeldExtension> modifierExtensions, EncodeContext theEncodeContext) throws IOException {
1279                if (extensions.isEmpty() == false) {
1280                        theEncodeContext.pushPath("extension", false);
1281                        beginArray(theEventWriter, "extension");
1282                        for (HeldExtension next : extensions) {
1283                                next.write(resDef, theResource, theEventWriter, theEncodeContext);
1284                        }
1285                        theEventWriter.endArray();
1286                        theEncodeContext.popPath();
1287                }
1288                if (modifierExtensions.isEmpty() == false) {
1289                        theEncodeContext.pushPath("modifierExtension", false);
1290                        beginArray(theEventWriter, "modifierExtension");
1291                        for (HeldExtension next : modifierExtensions) {
1292                                next.write(resDef, theResource, theEventWriter, theEncodeContext);
1293                        }
1294                        theEventWriter.endArray();
1295                        theEncodeContext.popPath();
1296                }
1297        }
1298
1299        private void writeOptionalTagWithTextNode(JsonLikeWriter theEventWriter, String theElementName, IPrimitiveDatatype<?> thePrimitive) throws IOException {
1300                if (thePrimitive == null) {
1301                        return;
1302                }
1303                String str = thePrimitive.getValueAsString();
1304                writeOptionalTagWithTextNode(theEventWriter, theElementName, str);
1305        }
1306
1307        private void writeOptionalTagWithTextNode(JsonLikeWriter theEventWriter, String theElementName, String theValue) throws IOException {
1308                if (StringUtils.isNotBlank(theValue)) {
1309                        write(theEventWriter, theElementName, theValue);
1310                }
1311        }
1312
1313        public static Gson newGson() {
1314                Gson gson = new GsonBuilder().disableHtmlEscaping().create();
1315                return gson;
1316        }
1317
1318        private static void write(JsonLikeWriter theWriter, String theName, String theValue) throws IOException {
1319                theWriter.write(theName, theValue);
1320        }
1321
1322        private class HeldExtension implements Comparable<HeldExtension> {
1323
1324                private CompositeChildElement myChildElem;
1325                private RuntimeChildDeclaredExtensionDefinition myDef;
1326                private boolean myModifier;
1327                private IBaseExtension<?, ?> myUndeclaredExtension;
1328                private IBase myValue;
1329                private CompositeChildElement myParent;
1330
1331                public HeldExtension(IBaseExtension<?, ?> theUndeclaredExtension, boolean theModifier, CompositeChildElement theChildElem, CompositeChildElement theParent) {
1332                        assert theUndeclaredExtension != null;
1333                        myUndeclaredExtension = theUndeclaredExtension;
1334                        myModifier = theModifier;
1335                        myChildElem = theChildElem;
1336                        myParent = theParent;
1337                }
1338
1339                public HeldExtension(RuntimeChildDeclaredExtensionDefinition theDef, IBase theValue, CompositeChildElement theChildElem) {
1340                        assert theDef != null;
1341                        assert theValue != null;
1342                        myDef = theDef;
1343                        myValue = theValue;
1344                        myChildElem = theChildElem;
1345                }
1346
1347                @Override
1348                public int compareTo(HeldExtension theArg0) {
1349                        String url1 = myDef != null ? myDef.getExtensionUrl() : myUndeclaredExtension.getUrl();
1350                        String url2 = theArg0.myDef != null ? theArg0.myDef.getExtensionUrl() : theArg0.myUndeclaredExtension.getUrl();
1351                        url1 = defaultString(getExtensionUrl(url1));
1352                        url2 = defaultString(getExtensionUrl(url2));
1353                        return url1.compareTo(url2);
1354                }
1355
1356                private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName, EncodeContext theEncodeContext) throws IOException {
1357                        if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
1358                                final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
1359                                final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
1360                                // Undeclared extensions
1361                                extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null);
1362                                // Declared extensions
1363                                if (def != null) {
1364                                        extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent);
1365                                }
1366                                boolean haveContent = false;
1367                                if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) {
1368                                        haveContent = true;
1369                                }
1370                                if (haveContent) {
1371                                        beginObject(theEventWriter, '_' + childName);
1372                                        writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions, theEncodeContext);
1373                                        theEventWriter.endObject();
1374                                }
1375                        }
1376                }
1377
1378                public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, EncodeContext theEncodeContext) throws IOException {
1379                        if (myUndeclaredExtension != null) {
1380                                writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension, theEncodeContext);
1381                        } else {
1382                                theEventWriter.beginObject();
1383
1384                                writeCommentsPreAndPost(myValue, theEventWriter);
1385
1386                                JsonParser.write(theEventWriter, "url", getExtensionUrl(myDef.getExtensionUrl()));
1387
1388                                /*
1389                                 * This makes sure that even if the extension contains a reference to a contained
1390                                 * resource which has a HAPI-assigned ID we'll still encode that ID.
1391                                 *
1392                                 * See #327
1393                                 */
1394                                List<? extends IBase> preProcessedValue = preProcessValues(myDef, theResource, Collections.singletonList(myValue), myChildElem, theEncodeContext);
1395
1396                                // // Check for undeclared extensions on the declared extension
1397                                // // (grrrrrr....)
1398                                // if (myValue instanceof ISupportsUndeclaredExtensions) {
1399                                // ISupportsUndeclaredExtensions value = (ISupportsUndeclaredExtensions)myValue;
1400                                // List<ExtensionDt> exts = value.getUndeclaredExtensions();
1401                                // if (exts.size() > 0) {
1402                                // ArrayList<IBase> newValueList = new ArrayList<IBase>();
1403                                // newValueList.addAll(preProcessedValue);
1404                                // newValueList.addAll(exts);
1405                                // preProcessedValue = newValueList;
1406                                // }
1407                                // }
1408
1409                                myValue = preProcessedValue.get(0);
1410
1411                                BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
1412                                if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
1413                                        extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource, myChildElem, null, theEncodeContext);
1414                                } else {
1415                                        String childName = myDef.getChildNameByDatatype(myValue.getClass());
1416                                        encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false,  myParent, false, theEncodeContext);
1417                                        managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName, theEncodeContext);
1418                                }
1419
1420                                theEventWriter.endObject();
1421                        }
1422                }
1423
1424                private void writeUndeclaredExtension(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBaseExtension<?, ?> ext, EncodeContext theEncodeContext) throws IOException {
1425                        IBase value = ext.getValue();
1426                        final String extensionUrl = getExtensionUrl(ext.getUrl());
1427
1428                        theEventWriter.beginObject();
1429
1430                        writeCommentsPreAndPost(myUndeclaredExtension, theEventWriter);
1431
1432                        String elementId = getCompositeElementId(ext);
1433                        if (isNotBlank(elementId)) {
1434                                JsonParser.write(theEventWriter, "id", getCompositeElementId(ext));
1435                        }
1436
1437                        JsonParser.write(theEventWriter, "url", extensionUrl);
1438
1439                        boolean noValue = value == null || value.isEmpty();
1440                        if (noValue && ext.getExtension().isEmpty()) {
1441                                ourLog.debug("Extension with URL[{}] has no value", extensionUrl);
1442                        } else if (noValue) {
1443
1444                                if (myModifier) {
1445                                        beginArray(theEventWriter, "modifierExtension");
1446                                } else {
1447                                        beginArray(theEventWriter, "extension");
1448                                }
1449
1450                                for (Object next : ext.getExtension()) {
1451                                        writeUndeclaredExtension(theResDef, theResource, theEventWriter, (IBaseExtension<?, ?>) next, theEncodeContext);
1452                                }
1453                                theEventWriter.endArray();
1454                        } else {
1455
1456                                /*
1457                                 * Pre-process value - This is called in case the value is a reference
1458                                 * since we might modify the text
1459                                 */
1460                                value = preProcessValues(myDef, theResource, Collections.singletonList(value), myChildElem, theEncodeContext).get(0);
1461
1462                                RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
1463                                String childName = extDef.getChildNameByDatatype(value.getClass());
1464                                if (childName == null) {
1465                                        childName = "value" + WordUtils.capitalize(myContext.getElementDefinition(value.getClass()).getName());
1466                                }
1467                                BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
1468                                if (childDef == null) {
1469                                        throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
1470                                }
1471                                encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, false, myParent,false, theEncodeContext);
1472                                managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName, theEncodeContext);
1473                        }
1474
1475                        // theEventWriter.name(myUndeclaredExtension.get);
1476
1477                        theEventWriter.endObject();
1478                }
1479        }
1480}