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