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