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