001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.parser;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
025import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
026import ca.uhn.fhir.context.ConfigurationException;
027import ca.uhn.fhir.context.FhirContext;
028import ca.uhn.fhir.context.FhirVersionEnum;
029import ca.uhn.fhir.context.RuntimeChildContainedResources;
030import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
031import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
032import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
033import ca.uhn.fhir.context.RuntimeResourceDefinition;
034import ca.uhn.fhir.i18n.Msg;
035import ca.uhn.fhir.model.api.ExtensionDt;
036import ca.uhn.fhir.model.api.IPrimitiveDatatype;
037import ca.uhn.fhir.model.api.IResource;
038import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
039import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
040import ca.uhn.fhir.model.api.Tag;
041import ca.uhn.fhir.model.api.TagList;
042import ca.uhn.fhir.model.base.composite.BaseCodingDt;
043import ca.uhn.fhir.model.base.composite.BaseContainedDt;
044import ca.uhn.fhir.model.primitive.IdDt;
045import ca.uhn.fhir.model.primitive.InstantDt;
046import ca.uhn.fhir.narrative.INarrativeGenerator;
047import ca.uhn.fhir.parser.json.BaseJsonLikeArray;
048import ca.uhn.fhir.parser.json.BaseJsonLikeObject;
049import ca.uhn.fhir.parser.json.BaseJsonLikeValue;
050import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ScalarType;
051import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ValueType;
052import ca.uhn.fhir.parser.json.BaseJsonLikeWriter;
053import ca.uhn.fhir.parser.json.JsonLikeStructure;
054import ca.uhn.fhir.parser.json.jackson.JacksonStructure;
055import ca.uhn.fhir.rest.api.EncodingEnum;
056import ca.uhn.fhir.util.ElementUtil;
057import ca.uhn.fhir.util.FhirTerser;
058import jakarta.annotation.Nonnull;
059import org.apache.commons.lang3.StringUtils;
060import org.apache.commons.lang3.Validate;
061import org.apache.commons.text.WordUtils;
062import org.hl7.fhir.instance.model.api.IBase;
063import org.hl7.fhir.instance.model.api.IBaseBooleanDatatype;
064import org.hl7.fhir.instance.model.api.IBaseDecimalDatatype;
065import org.hl7.fhir.instance.model.api.IBaseExtension;
066import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
067import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
068import org.hl7.fhir.instance.model.api.IBaseIntegerDatatype;
069import org.hl7.fhir.instance.model.api.IBaseResource;
070import org.hl7.fhir.instance.model.api.IDomainResource;
071import org.hl7.fhir.instance.model.api.IIdType;
072import org.hl7.fhir.instance.model.api.INarrative;
073import org.hl7.fhir.instance.model.api.IPrimitiveType;
074
075import java.io.IOException;
076import java.io.Reader;
077import java.io.Writer;
078import java.math.BigDecimal;
079import java.util.ArrayList;
080import java.util.Collections;
081import java.util.Iterator;
082import java.util.List;
083import java.util.Map;
084
085import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE;
086import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE;
087import static org.apache.commons.lang3.StringUtils.defaultString;
088import static org.apache.commons.lang3.StringUtils.isBlank;
089import static org.apache.commons.lang3.StringUtils.isNotBlank;
090
091/**
092 * This class is the FHIR JSON parser/encoder. Users should not interact with this class directly, but should use
093 * {@link FhirContext#newJsonParser()} to get an instance.
094 */
095public class JsonParser extends BaseParser implements IJsonLikeParser {
096
097        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParser.HeldExtension.class);
098
099        private boolean myPrettyPrint;
100
101        private Boolean myIsSupportsFhirComment;
102
103        /**
104         * Do not use this constructor, the recommended way to obtain a new instance of the JSON parser is to invoke
105         * {@link FhirContext#newJsonParser()}.
106         *
107         * @param theParserErrorHandler
108         */
109        public JsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
110                super(theContext, theParserErrorHandler);
111        }
112
113        private boolean addToHeldComments(
114                        int valueIdx, List<String> theCommentsToAdd, ArrayList<ArrayList<String>> theListToAddTo) {
115                if (theCommentsToAdd.size() > 0) {
116                        theListToAddTo.ensureCapacity(valueIdx);
117                        while (theListToAddTo.size() <= valueIdx) {
118                                theListToAddTo.add(null);
119                        }
120                        if (theListToAddTo.get(valueIdx) == null) {
121                                theListToAddTo.set(valueIdx, new ArrayList<>());
122                        }
123                        theListToAddTo.get(valueIdx).addAll(theCommentsToAdd);
124                        return true;
125                }
126                return false;
127        }
128
129        private boolean addToHeldExtensions(
130                        int valueIdx,
131                        List<? extends IBaseExtension<?, ?>> ext,
132                        ArrayList<ArrayList<HeldExtension>> list,
133                        boolean theIsModifier,
134                        CompositeChildElement theChildElem,
135                        CompositeChildElement theParent,
136                        EncodeContext theEncodeContext,
137                        boolean theContainedResource,
138                        IBase theContainingElement) {
139                boolean retVal = false;
140                if (ext.size() > 0) {
141                        Boolean encodeExtension = null;
142                        for (IBaseExtension<?, ?> next : ext) {
143
144                                if (next.isEmpty()) {
145                                        continue;
146                                }
147
148                                // Make sure we respect _summary and _elements
149                                if (encodeExtension == null) {
150                                        encodeExtension =
151                                                        isEncodeExtension(theParent, theEncodeContext, theContainedResource, theContainingElement);
152                                }
153
154                                if (encodeExtension) {
155                                        HeldExtension extension = new HeldExtension(next, theIsModifier, theChildElem, theParent);
156                                        list.ensureCapacity(valueIdx);
157                                        while (list.size() <= valueIdx) {
158                                                list.add(null);
159                                        }
160                                        ArrayList<HeldExtension> extensionList = list.get(valueIdx);
161                                        if (extensionList == null) {
162                                                extensionList = new ArrayList<>();
163                                                list.set(valueIdx, extensionList);
164                                        }
165                                        extensionList.add(extension);
166                                        retVal = true;
167                                }
168                        }
169                }
170                return retVal;
171        }
172
173        private void addToHeldIds(int theValueIdx, ArrayList<String> theListToAddTo, String theId) {
174                theListToAddTo.ensureCapacity(theValueIdx);
175                while (theListToAddTo.size() <= theValueIdx) {
176                        theListToAddTo.add(null);
177                }
178                if (theListToAddTo.get(theValueIdx) == null) {
179                        theListToAddTo.set(theValueIdx, theId);
180                }
181        }
182
183        // private void assertObjectOfType(JsonLikeValue theResourceTypeObj, Object theValueType, String thePosition) {
184        // if (theResourceTypeObj == null) {
185        // throw new DataFormatException(Msg.code(1836) + "Invalid JSON content detected, missing required element: '" +
186        // thePosition + "'");
187        // }
188        //
189        // if (theResourceTypeObj.getValueType() != theValueType) {
190        // throw new DataFormatException(Msg.code(1837) + "Invalid content of element " + thePosition + ", expected " +
191        // theValueType);
192        // }
193        // }
194
195        private void beginArray(BaseJsonLikeWriter theEventWriter, String arrayName) throws IOException {
196                theEventWriter.beginArray(arrayName);
197        }
198
199        private void beginObject(BaseJsonLikeWriter theEventWriter, String arrayName) throws IOException {
200                theEventWriter.beginObject(arrayName);
201        }
202
203        private BaseJsonLikeWriter createJsonWriter(Writer theWriter) throws IOException {
204                JsonLikeStructure jsonStructure = new JacksonStructure();
205                return jsonStructure.getJsonLikeWriter(theWriter);
206        }
207
208        public void doEncodeResourceToJsonLikeWriter(
209                        IBaseResource theResource, BaseJsonLikeWriter theEventWriter, EncodeContext theEncodeContext)
210                        throws IOException {
211                if (myPrettyPrint) {
212                        theEventWriter.setPrettyPrint(myPrettyPrint);
213                }
214                theEventWriter.init();
215
216                RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
217                encodeResourceToJsonStreamWriter(resDef, theResource, theEventWriter, null, false, theEncodeContext);
218                theEventWriter.flush();
219        }
220
221        @Override
222        protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext)
223                        throws IOException {
224                BaseJsonLikeWriter eventWriter = createJsonWriter(theWriter);
225                doEncodeResourceToJsonLikeWriter(theResource, eventWriter, theEncodeContext);
226                eventWriter.close();
227        }
228
229        @Override
230        protected void doEncodeToWriter(IBase theElement, Writer theWriter, EncodeContext theEncodeContext)
231                        throws IOException, DataFormatException {
232                BaseJsonLikeWriter eventWriter = createJsonWriter(theWriter);
233                eventWriter.beginObject();
234                encodeCompositeElementToStreamWriter(null, null, theElement, eventWriter, false, null, theEncodeContext);
235                eventWriter.endObject();
236                eventWriter.close();
237        }
238
239        @Override
240        protected void doParseIntoComplexStructure(Reader theSource, IBase theTarget) {
241                JsonLikeStructure jsonStructure = new JacksonStructure();
242                jsonStructure.load(theSource);
243
244                ParserState<IBase> state =
245                                ParserState.getComplexObjectState(this, getContext(), getContext(), true, theTarget, getErrorHandler());
246                state.enteringNewElement(null, null);
247
248                parseChildren(jsonStructure.getRootObject(), state);
249
250                state.endingElement();
251        }
252
253        @Override
254        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
255                JsonLikeStructure jsonStructure = new JacksonStructure();
256                jsonStructure.load(theReader);
257
258                T retVal = doParseResource(theResourceType, jsonStructure);
259
260                return retVal;
261        }
262
263        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, JsonLikeStructure theJsonStructure) {
264                BaseJsonLikeObject object = theJsonStructure.getRootObject();
265
266                BaseJsonLikeValue resourceTypeObj = object.get("resourceType");
267                if (resourceTypeObj == null || !resourceTypeObj.isString() || isBlank(resourceTypeObj.getAsString())) {
268                        throw new DataFormatException(
269                                        Msg.code(1838) + "Invalid JSON content detected, missing required element: 'resourceType'");
270                }
271
272                String resourceType = resourceTypeObj.getAsString();
273
274                ParserState<? extends IBaseResource> state =
275                                ParserState.getPreResourceInstance(this, theResourceType, getContext(), true, getErrorHandler());
276                state.enteringNewElement(null, resourceType);
277
278                parseChildren(object, state);
279
280                state.endingElement();
281                state.endingElement();
282
283                @SuppressWarnings("unchecked")
284                T retVal = (T) state.getObject();
285
286                return retVal;
287        }
288
289        private void encodeChildElementToStreamWriter(
290                        RuntimeResourceDefinition theResDef,
291                        IBaseResource theResource,
292                        BaseJsonLikeWriter theEventWriter,
293                        IBase theNextValue,
294                        BaseRuntimeElementDefinition<?> theChildDef,
295                        String theChildName,
296                        boolean theContainedResource,
297                        CompositeChildElement theChildElem,
298                        boolean theForceEmpty,
299                        EncodeContext theEncodeContext)
300                        throws IOException {
301
302                switch (theChildDef.getChildType()) {
303                        case EXTENSION_DECLARED:
304                                break;
305                        case ID_DATATYPE: {
306                                IIdType value = (IIdType) theNextValue;
307                                String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
308                                if (isBlank(encodedValue)) {
309                                        break;
310                                }
311                                if (theChildName != null) {
312                                        write(theEventWriter, theChildName, encodedValue);
313                                } else {
314                                        theEventWriter.write(encodedValue);
315                                }
316                                break;
317                        }
318                        case PRIMITIVE_DATATYPE: {
319                                final IPrimitiveType<?> value = (IPrimitiveType<?>) theNextValue;
320                                final String valueStr = value.getValueAsString();
321                                if (isBlank(valueStr)) {
322                                        if (theForceEmpty) {
323                                                theEventWriter.writeNull();
324                                        }
325                                        break;
326                                }
327
328                                // check for the common case first - String value types
329                                Object valueObj = value.getValue();
330                                if (valueObj instanceof String) {
331                                        if (theChildName != null) {
332                                                theEventWriter.write(theChildName, valueStr);
333                                        } else {
334                                                theEventWriter.write(valueStr);
335                                        }
336                                        break;
337                                } else if (valueObj instanceof Long) {
338                                        if (theChildName != null) {
339                                                theEventWriter.write(theChildName, (long) valueObj);
340                                        } else {
341                                                theEventWriter.write((long) valueObj);
342                                        }
343                                        break;
344                                }
345
346                                if (value instanceof IBaseIntegerDatatype) {
347                                        if (theChildName != null) {
348                                                write(theEventWriter, theChildName, ((IBaseIntegerDatatype) value).getValue());
349                                        } else {
350                                                theEventWriter.write(((IBaseIntegerDatatype) value).getValue());
351                                        }
352                                } else if (value instanceof IBaseDecimalDatatype) {
353                                        BigDecimal decimalValue = ((IBaseDecimalDatatype) value).getValue();
354                                        decimalValue = new BigDecimal(decimalValue.toString()) {
355                                                private static final long serialVersionUID = 1L;
356
357                                                @Override
358                                                public String toString() {
359                                                        return value.getValueAsString();
360                                                }
361                                        };
362                                        if (theChildName != null) {
363                                                write(theEventWriter, theChildName, decimalValue);
364                                        } else {
365                                                theEventWriter.write(decimalValue);
366                                        }
367                                } else if (value instanceof IBaseBooleanDatatype) {
368                                        if (theChildName != null) {
369                                                write(theEventWriter, theChildName, ((IBaseBooleanDatatype) value).getValue());
370                                        } else {
371                                                Boolean booleanValue = ((IBaseBooleanDatatype) value).getValue();
372                                                if (booleanValue != null) {
373                                                        theEventWriter.write(booleanValue.booleanValue());
374                                                }
375                                        }
376                                } else {
377                                        if (theChildName != null) {
378                                                write(theEventWriter, theChildName, valueStr);
379                                        } else {
380                                                theEventWriter.write(valueStr);
381                                        }
382                                }
383                                break;
384                        }
385                        case RESOURCE_BLOCK:
386                        case COMPOSITE_DATATYPE: {
387                                if (theChildName != null) {
388                                        theEventWriter.beginObject(theChildName);
389                                } else {
390                                        theEventWriter.beginObject();
391                                }
392                                encodeCompositeElementToStreamWriter(
393                                                theResDef,
394                                                theResource,
395                                                theNextValue,
396                                                theEventWriter,
397                                                theContainedResource,
398                                                theChildElem,
399                                                theEncodeContext);
400                                theEventWriter.endObject();
401                                break;
402                        }
403                        case CONTAINED_RESOURCE_LIST:
404                        case CONTAINED_RESOURCES: {
405                                List<IBaseResource> containedResources =
406                                                theEncodeContext.getContainedResources().getContainedResources();
407                                if (containedResources.size() > 0) {
408                                        beginArray(theEventWriter, theChildName);
409
410                                        for (IBaseResource next : containedResources) {
411                                                IIdType resourceId =
412                                                                theEncodeContext.getContainedResources().getResourceId(next);
413                                                String value = resourceId.getValue();
414                                                encodeResourceToJsonStreamWriter(
415                                                                theResDef,
416                                                                next,
417                                                                theEventWriter,
418                                                                null,
419                                                                true,
420                                                                fixContainedResourceId(value),
421                                                                theEncodeContext);
422                                        }
423
424                                        theEventWriter.endArray();
425                                }
426                                break;
427                        }
428                        case PRIMITIVE_XHTML_HL7ORG:
429                        case PRIMITIVE_XHTML: {
430                                if (!isSuppressNarratives()) {
431                                        IPrimitiveType<?> dt = (IPrimitiveType<?>) theNextValue;
432                                        if (theChildName != null) {
433                                                write(theEventWriter, theChildName, dt.getValueAsString());
434                                        } else {
435                                                theEventWriter.write(dt.getValueAsString());
436                                        }
437                                } else {
438                                        if (theChildName != null) {
439                                                // do nothing
440                                        } else {
441                                                theEventWriter.writeNull();
442                                        }
443                                }
444                                break;
445                        }
446                        case RESOURCE:
447                                IBaseResource resource = (IBaseResource) theNextValue;
448                                RuntimeResourceDefinition def = getContext().getResourceDefinition(resource);
449
450                                theEncodeContext.pushPath(def.getName(), true);
451                                encodeResourceToJsonStreamWriter(
452                                                def, resource, theEventWriter, theChildName, theContainedResource, theEncodeContext);
453                                theEncodeContext.popPath();
454
455                                break;
456                        case UNDECL_EXT:
457                        default:
458                                throw new IllegalStateException(Msg.code(1839) + "Should not have this state here: "
459                                                + theChildDef.getChildType().name());
460                }
461        }
462
463        private void encodeCompositeElementChildrenToStreamWriter(
464                        RuntimeResourceDefinition theResDef,
465                        IBaseResource theResource,
466                        IBase theElement,
467                        BaseJsonLikeWriter theEventWriter,
468                        boolean theContainedResource,
469                        CompositeChildElement theParent,
470                        EncodeContext theEncodeContext)
471                        throws IOException {
472
473                {
474                        String elementId = getCompositeElementId(theElement);
475                        if (isNotBlank(elementId)) {
476                                write(theEventWriter, "id", elementId);
477                        }
478                }
479
480                boolean haveWrittenExtensions = false;
481                Iterable<CompositeChildElement> compositeChildElements =
482                                super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext);
483                for (CompositeChildElement nextChildElem : compositeChildElements) {
484
485                        BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
486
487                        if (nextChildElem.getDef().getElementName().equals("extension")
488                                        || nextChildElem.getDef().getElementName().equals("modifierExtension")
489                                        || nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
490                                if (!haveWrittenExtensions) {
491                                        extractAndWriteExtensionsAsDirectChild(
492                                                        theElement,
493                                                        theEventWriter,
494                                                        getContext().getElementDefinition(theElement.getClass()),
495                                                        theResDef,
496                                                        theResource,
497                                                        nextChildElem,
498                                                        theParent,
499                                                        theEncodeContext,
500                                                        theContainedResource);
501                                        haveWrittenExtensions = true;
502                                }
503                                continue;
504                        }
505
506                        if (nextChild instanceof RuntimeChildNarrativeDefinition) {
507                                INarrativeGenerator gen = getContext().getNarrativeGenerator();
508                                if (gen != null) {
509                                        INarrative narr;
510                                        if (theResource instanceof IResource) {
511                                                narr = ((IResource) theResource).getText();
512                                        } else if (theResource instanceof IDomainResource) {
513                                                narr = ((IDomainResource) theResource).getText();
514                                        } else {
515                                                narr = null;
516                                        }
517                                        if (narr != null && narr.isEmpty()) {
518                                                gen.populateResourceNarrative(getContext(), theResource);
519                                                if (!narr.isEmpty()) {
520                                                        RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
521                                                        String childName = nextChild.getChildNameByDatatype(child.getDatatype());
522                                                        BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
523                                                        encodeChildElementToStreamWriter(
524                                                                        theResDef,
525                                                                        theResource,
526                                                                        theEventWriter,
527                                                                        narr,
528                                                                        type,
529                                                                        childName,
530                                                                        theContainedResource,
531                                                                        nextChildElem,
532                                                                        false,
533                                                                        theEncodeContext);
534                                                        continue;
535                                                }
536                                        }
537                                }
538                        } else if (nextChild instanceof RuntimeChildContainedResources) {
539                                String childName = nextChild.getValidChildNames().iterator().next();
540                                BaseRuntimeElementDefinition<?> child = nextChild.getChildByName(childName);
541                                encodeChildElementToStreamWriter(
542                                                theResDef,
543                                                theResource,
544                                                theEventWriter,
545                                                null,
546                                                child,
547                                                childName,
548                                                theContainedResource,
549                                                nextChildElem,
550                                                false,
551                                                theEncodeContext);
552                                continue;
553                        }
554
555                        List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
556                        values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
557
558                        if (values == null || values.isEmpty()) {
559                                continue;
560                        }
561
562                        String currentChildName = null;
563                        boolean inArray = false;
564
565                        ArrayList<ArrayList<HeldExtension>> extensions = new ArrayList<>(0);
566                        ArrayList<ArrayList<HeldExtension>> modifierExtensions = new ArrayList<>(0);
567                        ArrayList<ArrayList<String>> comments = new ArrayList<>(0);
568                        ArrayList<String> ids = new ArrayList<>(0);
569
570                        int valueIdx = 0;
571                        for (IBase nextValue : values) {
572
573                                if (nextValue == null || nextValue.isEmpty()) {
574                                        if (nextValue instanceof BaseContainedDt) {
575                                                if (theContainedResource
576                                                                || theEncodeContext.getContainedResources().isEmpty()) {
577                                                        continue;
578                                                }
579                                        } else {
580                                                continue;
581                                        }
582                                }
583
584                                BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
585                                if (childNameAndDef == null) {
586                                        continue;
587                                }
588
589                                /*
590                                 * Often the two values below will be the same thing. There are cases though
591                                 * where they will not be. An example would be Observation.value, which is
592                                 * a choice type. If the value contains a Quantity, then:
593                                 * nextChildGenericName = "value"
594                                 * nextChildSpecificName = "valueQuantity"
595                                 */
596                                String nextChildSpecificName = childNameAndDef.getChildName();
597                                String nextChildGenericName = nextChild.getElementName();
598
599                                theEncodeContext.pushPath(nextChildGenericName, false);
600
601                                BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
602                                boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
603
604                                if ((childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES
605                                                                || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST)
606                                                && theContainedResource) {
607                                        continue;
608                                }
609
610                                boolean force = false;
611                                if (primitive) {
612                                        if (nextValue instanceof ISupportsUndeclaredExtensions) {
613                                                List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions();
614                                                force |= addToHeldExtensions(
615                                                                valueIdx,
616                                                                ext,
617                                                                extensions,
618                                                                false,
619                                                                nextChildElem,
620                                                                theParent,
621                                                                theEncodeContext,
622                                                                theContainedResource,
623                                                                theElement);
624
625                                                ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions();
626                                                force |= addToHeldExtensions(
627                                                                valueIdx,
628                                                                ext,
629                                                                modifierExtensions,
630                                                                true,
631                                                                nextChildElem,
632                                                                theParent,
633                                                                theEncodeContext,
634                                                                theContainedResource,
635                                                                theElement);
636                                        } else {
637                                                if (nextValue instanceof IBaseHasExtensions) {
638                                                        IBaseHasExtensions element = (IBaseHasExtensions) nextValue;
639                                                        List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
640                                                        force |= addToHeldExtensions(
641                                                                        valueIdx,
642                                                                        ext,
643                                                                        extensions,
644                                                                        false,
645                                                                        nextChildElem,
646                                                                        theParent,
647                                                                        theEncodeContext,
648                                                                        theContainedResource,
649                                                                        theElement);
650                                                }
651                                                if (nextValue instanceof IBaseHasModifierExtensions) {
652                                                        IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) nextValue;
653                                                        List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
654                                                        force |= addToHeldExtensions(
655                                                                        valueIdx,
656                                                                        ext,
657                                                                        modifierExtensions,
658                                                                        true,
659                                                                        nextChildElem,
660                                                                        theParent,
661                                                                        theEncodeContext,
662                                                                        theContainedResource,
663                                                                        theElement);
664                                                }
665                                        }
666                                        if (nextValue.hasFormatComment()) {
667                                                force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPre(), comments);
668                                                force |= addToHeldComments(valueIdx, nextValue.getFormatCommentsPost(), comments);
669                                        }
670                                        String elementId = getCompositeElementId(nextValue);
671                                        if (isNotBlank(elementId)) {
672                                                force = true;
673                                                addToHeldIds(valueIdx, ids, elementId);
674                                        }
675                                }
676
677                                if (currentChildName == null || !currentChildName.equals(nextChildSpecificName)) {
678                                        if (inArray) {
679                                                theEventWriter.endArray();
680                                        }
681                                        BaseRuntimeChildDefinition replacedParentDefinition = nextChild.getReplacedParentDefinition();
682                                        if (nextChild.isMultipleCardinality()
683                                                        || (replacedParentDefinition != null && replacedParentDefinition.isMultipleCardinality())) {
684                                                beginArray(theEventWriter, nextChildSpecificName);
685                                                inArray = true;
686                                                encodeChildElementToStreamWriter(
687                                                                theResDef,
688                                                                theResource,
689                                                                theEventWriter,
690                                                                nextValue,
691                                                                childDef,
692                                                                null,
693                                                                theContainedResource,
694                                                                nextChildElem,
695                                                                force,
696                                                                theEncodeContext);
697                                        } else {
698                                                encodeChildElementToStreamWriter(
699                                                                theResDef,
700                                                                theResource,
701                                                                theEventWriter,
702                                                                nextValue,
703                                                                childDef,
704                                                                nextChildSpecificName,
705                                                                theContainedResource,
706                                                                nextChildElem,
707                                                                false,
708                                                                theEncodeContext);
709                                        }
710                                        currentChildName = nextChildSpecificName;
711                                } else {
712                                        encodeChildElementToStreamWriter(
713                                                        theResDef,
714                                                        theResource,
715                                                        theEventWriter,
716                                                        nextValue,
717                                                        childDef,
718                                                        null,
719                                                        theContainedResource,
720                                                        nextChildElem,
721                                                        force,
722                                                        theEncodeContext);
723                                }
724
725                                valueIdx++;
726                                theEncodeContext.popPath();
727                        }
728
729                        if (inArray) {
730                                theEventWriter.endArray();
731                        }
732
733                        if (!extensions.isEmpty()
734                                        || !modifierExtensions.isEmpty()
735                                        || (!comments.isEmpty() && isSupportsFhirComment())) {
736                                if (inArray) {
737                                        // If this is a repeatable field, the extensions go in an array too
738                                        beginArray(theEventWriter, '_' + currentChildName);
739                                } else {
740                                        beginObject(theEventWriter, '_' + currentChildName);
741                                }
742
743                                for (int i = 0; i < valueIdx; i++) {
744                                        boolean haveContent = false;
745
746                                        List<HeldExtension> heldExts = Collections.emptyList();
747                                        List<HeldExtension> heldModExts = Collections.emptyList();
748                                        if (extensions.size() > i
749                                                        && extensions.get(i) != null
750                                                        && !extensions.get(i).isEmpty()) {
751                                                haveContent = true;
752                                                heldExts = extensions.get(i);
753                                        }
754
755                                        if (modifierExtensions.size() > i
756                                                        && modifierExtensions.get(i) != null
757                                                        && !modifierExtensions.get(i).isEmpty()) {
758                                                haveContent = true;
759                                                heldModExts = modifierExtensions.get(i);
760                                        }
761
762                                        ArrayList<String> nextComments;
763                                        if (comments.size() > i) {
764                                                nextComments = comments.get(i);
765                                        } else {
766                                                nextComments = null;
767                                        }
768                                        if (nextComments != null && !nextComments.isEmpty()) {
769                                                haveContent = true;
770                                        }
771
772                                        String elementId = null;
773                                        if (ids.size() > i) {
774                                                elementId = ids.get(i);
775                                                haveContent |= isNotBlank(elementId);
776                                        }
777
778                                        if (!haveContent) {
779                                                theEventWriter.writeNull();
780                                        } else {
781                                                if (inArray) {
782                                                        theEventWriter.beginObject();
783                                                }
784                                                if (isNotBlank(elementId)) {
785                                                        write(theEventWriter, "id", elementId);
786                                                }
787                                                if (nextComments != null && !nextComments.isEmpty()) {
788                                                        if (isSupportsFhirComment()) {
789                                                                beginArray(theEventWriter, "fhir_comments");
790                                                                for (String next : nextComments) {
791                                                                        theEventWriter.write(next);
792                                                                }
793                                                                theEventWriter.endArray();
794                                                        }
795                                                }
796                                                writeExtensionsAsDirectChild(
797                                                                theResource,
798                                                                theEventWriter,
799                                                                theResDef,
800                                                                heldExts,
801                                                                heldModExts,
802                                                                theEncodeContext,
803                                                                theContainedResource);
804                                                if (inArray) {
805                                                        theEventWriter.endObject();
806                                                }
807                                        }
808                                }
809
810                                if (inArray) {
811                                        theEventWriter.endArray();
812                                } else {
813                                        theEventWriter.endObject();
814                                }
815                        }
816                }
817        }
818
819        private boolean isSupportsFhirComment() {
820                if (myIsSupportsFhirComment == null) {
821                        myIsSupportsFhirComment = !getContext().getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU2_1);
822                }
823                return myIsSupportsFhirComment;
824        }
825
826        private void encodeCompositeElementToStreamWriter(
827                        RuntimeResourceDefinition theResDef,
828                        IBaseResource theResource,
829                        IBase theNextValue,
830                        BaseJsonLikeWriter theEventWriter,
831                        boolean theContainedResource,
832                        CompositeChildElement theParent,
833                        EncodeContext theEncodeContext)
834                        throws IOException, DataFormatException {
835
836                writeCommentsPreAndPost(theNextValue, theEventWriter);
837                encodeCompositeElementChildrenToStreamWriter(
838                                theResDef,
839                                theResource,
840                                theNextValue,
841                                theEventWriter,
842                                theContainedResource,
843                                theParent,
844                                theEncodeContext);
845        }
846
847        @Override
848        public void encodeResourceToJsonLikeWriter(IBaseResource theResource, BaseJsonLikeWriter theJsonLikeWriter)
849                        throws IOException, DataFormatException {
850                Validate.notNull(theResource, "theResource can not be null");
851                Validate.notNull(theJsonLikeWriter, "theJsonLikeWriter can not be null");
852
853                if (theResource.getStructureFhirVersionEnum()
854                                != getContext().getVersion().getVersion()) {
855                        throw new IllegalArgumentException(Msg.code(1840) + "This parser is for FHIR version "
856                                        + getContext().getVersion().getVersion() + " - Can not encode a structure for version "
857                                        + theResource.getStructureFhirVersionEnum());
858                }
859
860                EncodeContext encodeContext =
861                                new EncodeContext(this, getContext().getParserOptions(), new FhirTerser.ContainedResources());
862                String resourceName = getContext().getResourceType(theResource);
863                encodeContext.pushPath(resourceName, true);
864                doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
865        }
866
867        private void encodeResourceToJsonStreamWriter(
868                        RuntimeResourceDefinition theResDef,
869                        IBaseResource theResource,
870                        BaseJsonLikeWriter theEventWriter,
871                        String theObjectNameOrNull,
872                        boolean theContainedResource,
873                        EncodeContext theEncodeContext)
874                        throws IOException {
875                IIdType resourceId = null;
876
877                if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
878                        resourceId = theResource.getIdElement();
879                        if (theResource.getIdElement().getValue().startsWith("urn:")) {
880                                resourceId = null;
881                        }
882                }
883
884                if (!theContainedResource) {
885                        if (!super.shouldEncodeResourceId(theResource, theEncodeContext)) {
886                                resourceId = null;
887                        } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
888                                resourceId = getEncodeForceResourceId();
889                        }
890                }
891
892                encodeResourceToJsonStreamWriter(
893                                theResDef,
894                                theResource,
895                                theEventWriter,
896                                theObjectNameOrNull,
897                                theContainedResource,
898                                resourceId,
899                                theEncodeContext);
900        }
901
902        private void encodeResourceToJsonStreamWriter(
903                        RuntimeResourceDefinition theResDef,
904                        IBaseResource theResource,
905                        BaseJsonLikeWriter theEventWriter,
906                        String theObjectNameOrNull,
907                        boolean theContainedResource,
908                        IIdType theResourceId,
909                        EncodeContext theEncodeContext)
910                        throws IOException {
911
912                if (!super.shouldEncodeResource(theResDef.getName(), theEncodeContext)) {
913                        return;
914                }
915
916                if (!theContainedResource) {
917                        containResourcesInReferences(theResource, theEncodeContext);
918                }
919
920                RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
921
922                if (theObjectNameOrNull == null) {
923                        theEventWriter.beginObject();
924                } else {
925                        beginObject(theEventWriter, theObjectNameOrNull);
926                }
927
928                write(theEventWriter, "resourceType", resDef.getName());
929                if (theResourceId != null && theResourceId.hasIdPart()) {
930                        write(theEventWriter, "id", theResourceId.getIdPart());
931                        final List<HeldExtension> extensions = new ArrayList<>(0);
932                        final List<HeldExtension> modifierExtensions = new ArrayList<>(0);
933                        // Undeclared extensions
934                        extractUndeclaredExtensions(
935                                        theResourceId, extensions, modifierExtensions, null, null, theEncodeContext, theContainedResource);
936                        boolean haveExtension = !extensions.isEmpty();
937
938                        if (theResourceId.hasFormatComment() || haveExtension) {
939                                beginObject(theEventWriter, "_id");
940                                if (theResourceId.hasFormatComment()) {
941                                        writeCommentsPreAndPost(theResourceId, theEventWriter);
942                                }
943                                if (haveExtension) {
944                                        writeExtensionsAsDirectChild(
945                                                        theResource,
946                                                        theEventWriter,
947                                                        theResDef,
948                                                        extensions,
949                                                        modifierExtensions,
950                                                        theEncodeContext,
951                                                        theContainedResource);
952                                }
953                                theEventWriter.endObject();
954                        }
955                }
956
957                if (theResource instanceof IResource) {
958                        parseMetaForDSTU2(theResDef, theResource, theEventWriter, theContainedResource, theEncodeContext, resDef);
959                }
960
961                encodeCompositeElementToStreamWriter(
962                                theResDef,
963                                theResource,
964                                theResource,
965                                theEventWriter,
966                                theContainedResource,
967                                new CompositeChildElement(resDef, theEncodeContext),
968                                theEncodeContext);
969
970                theEventWriter.endObject();
971        }
972
973        private void parseMetaForDSTU2(
974                        RuntimeResourceDefinition theResDef,
975                        IBaseResource theResource,
976                        BaseJsonLikeWriter theEventWriter,
977                        boolean theContainedResource,
978                        EncodeContext theEncodeContext,
979                        RuntimeResourceDefinition resDef)
980                        throws IOException {
981                IResource resource = (IResource) theResource;
982                // Object securityLabelRawObj =
983
984                List<BaseCodingDt> securityLabels =
985                                extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
986                List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
987                profiles = super.getProfileTagsForEncoding(resource, profiles);
988
989                TagList tags = getMetaTagsForEncoding(resource, theEncodeContext);
990                InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
991                IdDt resourceId = resource.getId();
992                String versionIdPart = resourceId.getVersionIdPart();
993                if (isBlank(versionIdPart)) {
994                        versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
995                }
996                List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys = getExtensionMetadataKeys(resource);
997
998                if (super.shouldEncodeResourceMeta(resource, theEncodeContext)
999                                                && (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false)
1000                                || !extensionMetadataKeys.isEmpty()) {
1001                        beginObject(theEventWriter, "meta");
1002
1003                        if (shouldEncodePath(resource, "meta.versionId", theEncodeContext)) {
1004                                writeOptionalTagWithTextNode(theEventWriter, "versionId", versionIdPart);
1005                        }
1006                        if (shouldEncodePath(resource, "meta.lastUpdated", theEncodeContext)) {
1007                                writeOptionalTagWithTextNode(theEventWriter, "lastUpdated", updated);
1008                        }
1009
1010                        if (profiles != null && profiles.isEmpty() == false) {
1011                                beginArray(theEventWriter, "profile");
1012                                for (IIdType profile : profiles) {
1013                                        if (profile != null && isNotBlank(profile.getValue())) {
1014                                                theEventWriter.write(profile.getValue());
1015                                        }
1016                                }
1017                                theEventWriter.endArray();
1018                        }
1019
1020                        if (securityLabels.isEmpty() == false) {
1021                                beginArray(theEventWriter, "security");
1022                                for (BaseCodingDt securityLabel : securityLabels) {
1023                                        theEventWriter.beginObject();
1024                                        theEncodeContext.pushPath("security", false);
1025                                        encodeCompositeElementChildrenToStreamWriter(
1026                                                        resDef,
1027                                                        resource,
1028                                                        securityLabel,
1029                                                        theEventWriter,
1030                                                        theContainedResource,
1031                                                        null,
1032                                                        theEncodeContext);
1033                                        theEncodeContext.popPath();
1034                                        theEventWriter.endObject();
1035                                }
1036                                theEventWriter.endArray();
1037                        }
1038
1039                        if (tags != null && tags.isEmpty() == false) {
1040                                beginArray(theEventWriter, "tag");
1041                                for (Tag tag : tags) {
1042                                        if (tag.isEmpty()) {
1043                                                continue;
1044                                        }
1045                                        theEventWriter.beginObject();
1046                                        writeOptionalTagWithTextNode(theEventWriter, "system", tag.getScheme());
1047                                        writeOptionalTagWithTextNode(theEventWriter, "code", tag.getTerm());
1048                                        writeOptionalTagWithTextNode(theEventWriter, "display", tag.getLabel());
1049                                        writeOptionalTagWithTextNode(theEventWriter, "version", tag.getVersion());
1050                                        write(theEventWriter, "userSelected", tag.getUserSelectedBoolean());
1051                                        theEventWriter.endObject();
1052                                }
1053                                theEventWriter.endArray();
1054                        }
1055
1056                        addExtensionMetadata(
1057                                        theResDef,
1058                                        theResource,
1059                                        theContainedResource,
1060                                        extensionMetadataKeys,
1061                                        resDef,
1062                                        theEventWriter,
1063                                        theEncodeContext);
1064
1065                        theEventWriter.endObject(); // end meta
1066                }
1067        }
1068
1069        private void addExtensionMetadata(
1070                        RuntimeResourceDefinition theResDef,
1071                        IBaseResource theResource,
1072                        boolean theContainedResource,
1073                        List<Map.Entry<ResourceMetadataKeyEnum<?>, Object>> extensionMetadataKeys,
1074                        RuntimeResourceDefinition resDef,
1075                        BaseJsonLikeWriter theEventWriter,
1076                        EncodeContext theEncodeContext)
1077                        throws IOException {
1078                if (extensionMetadataKeys.isEmpty()) {
1079                        return;
1080                }
1081
1082                ExtensionDt metaResource = new ExtensionDt();
1083                for (Map.Entry<ResourceMetadataKeyEnum<?>, Object> entry : extensionMetadataKeys) {
1084                        metaResource.addUndeclaredExtension((ExtensionDt) entry.getValue());
1085                }
1086                encodeCompositeElementToStreamWriter(
1087                                theResDef,
1088                                theResource,
1089                                metaResource,
1090                                theEventWriter,
1091                                theContainedResource,
1092                                new CompositeChildElement(resDef, theEncodeContext),
1093                                theEncodeContext);
1094        }
1095
1096        /**
1097         * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object
1098         * called _name): resource extensions, and extension extensions
1099         */
1100        private void extractAndWriteExtensionsAsDirectChild(
1101                        IBase theElement,
1102                        BaseJsonLikeWriter theEventWriter,
1103                        BaseRuntimeElementDefinition<?> theElementDef,
1104                        RuntimeResourceDefinition theResDef,
1105                        IBaseResource theResource,
1106                        CompositeChildElement theChildElem,
1107                        CompositeChildElement theParent,
1108                        EncodeContext theEncodeContext,
1109                        boolean theContainedResource)
1110                        throws IOException {
1111                List<HeldExtension> extensions = new ArrayList<>(0);
1112                List<HeldExtension> modifierExtensions = new ArrayList<>(0);
1113
1114                // Undeclared extensions
1115                extractUndeclaredExtensions(
1116                                theElement,
1117                                extensions,
1118                                modifierExtensions,
1119                                theChildElem,
1120                                theParent,
1121                                theEncodeContext,
1122                                theContainedResource);
1123
1124                // Declared extensions
1125                if (theElementDef != null) {
1126                        extractDeclaredExtensions(theElement, theElementDef, extensions, modifierExtensions, theChildElem);
1127                }
1128
1129                // Write the extensions
1130                writeExtensionsAsDirectChild(
1131                                theResource,
1132                                theEventWriter,
1133                                theResDef,
1134                                extensions,
1135                                modifierExtensions,
1136                                theEncodeContext,
1137                                theContainedResource);
1138        }
1139
1140        private void extractDeclaredExtensions(
1141                        IBase theResource,
1142                        BaseRuntimeElementDefinition<?> resDef,
1143                        List<HeldExtension> extensions,
1144                        List<HeldExtension> modifierExtensions,
1145                        CompositeChildElement theChildElem) {
1146                for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) {
1147                        for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
1148                                if (nextValue != null) {
1149                                        if (nextValue.isEmpty()) {
1150                                                continue;
1151                                        }
1152                                        extensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
1153                                }
1154                        }
1155                }
1156                for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) {
1157                        for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
1158                                if (nextValue != null) {
1159                                        if (nextValue.isEmpty()) {
1160                                                continue;
1161                                        }
1162                                        modifierExtensions.add(new HeldExtension(nextDef, nextValue, theChildElem));
1163                                }
1164                        }
1165                }
1166        }
1167
1168        private void extractUndeclaredExtensions(
1169                        IBase theElement,
1170                        List<HeldExtension> extensions,
1171                        List<HeldExtension> modifierExtensions,
1172                        CompositeChildElement theChildElem,
1173                        CompositeChildElement theParent,
1174                        EncodeContext theEncodeContext,
1175                        boolean theContainedResource) {
1176                if (theElement instanceof ISupportsUndeclaredExtensions) {
1177                        ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement;
1178                        List<ExtensionDt> ext = element.getUndeclaredExtensions();
1179                        for (ExtensionDt next : ext) {
1180                                if (next == null || next.isEmpty()) {
1181                                        continue;
1182                                }
1183                                extensions.add(new HeldExtension(next, false, theChildElem, theParent));
1184                        }
1185
1186                        ext = element.getUndeclaredModifierExtensions();
1187                        for (ExtensionDt next : ext) {
1188                                if (next == null || next.isEmpty()) {
1189                                        continue;
1190                                }
1191                                modifierExtensions.add(new HeldExtension(next, true, theChildElem, theParent));
1192                        }
1193                } else {
1194                        if (theElement instanceof IBaseHasExtensions) {
1195                                IBaseHasExtensions element = (IBaseHasExtensions) theElement;
1196                                List<? extends IBaseExtension<?, ?>> ext = element.getExtension();
1197                                Boolean encodeExtension = null;
1198                                for (IBaseExtension<?, ?> next : ext) {
1199                                        if (next == null
1200                                                        || (ElementUtil.isEmpty(next.getValue())
1201                                                                        && next.getExtension().isEmpty())) {
1202                                                continue;
1203                                        }
1204
1205                                        // Make sure we respect _elements and _summary
1206                                        if (encodeExtension == null) {
1207                                                encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, element);
1208                                        }
1209                                        if (encodeExtension) {
1210                                                HeldExtension extension = new HeldExtension(next, false, theChildElem, theParent);
1211                                                extensions.add(extension);
1212                                        }
1213                                }
1214                        }
1215                        if (theElement instanceof IBaseHasModifierExtensions) {
1216                                IBaseHasModifierExtensions element = (IBaseHasModifierExtensions) theElement;
1217                                List<? extends IBaseExtension<?, ?>> ext = element.getModifierExtension();
1218                                for (IBaseExtension<?, ?> next : ext) {
1219                                        if (next == null || next.isEmpty()) {
1220                                                continue;
1221                                        }
1222
1223                                        HeldExtension extension = new HeldExtension(next, true, theChildElem, theParent);
1224                                        modifierExtensions.add(extension);
1225                                }
1226                        }
1227                }
1228        }
1229
1230        private boolean isEncodeExtension(
1231                        CompositeChildElement theParent,
1232                        EncodeContext theEncodeContext,
1233                        boolean theContainedResource,
1234                        IBase theElement) {
1235                BaseRuntimeElementDefinition<?> runtimeElementDefinition =
1236                                getContext().getElementDefinition(theElement.getClass());
1237                boolean retVal = true;
1238                if (runtimeElementDefinition instanceof BaseRuntimeElementCompositeDefinition) {
1239                        BaseRuntimeElementCompositeDefinition definition =
1240                                        (BaseRuntimeElementCompositeDefinition) runtimeElementDefinition;
1241                        BaseRuntimeChildDefinition childDef = definition.getChildByName("extension");
1242                        CompositeChildElement c = new CompositeChildElement(theParent, childDef, theEncodeContext);
1243                        retVal = c.shouldBeEncoded(theContainedResource);
1244                }
1245                return retVal;
1246        }
1247
1248        @Override
1249        public EncodingEnum getEncoding() {
1250                return EncodingEnum.JSON;
1251        }
1252
1253        private BaseJsonLikeArray grabJsonArray(BaseJsonLikeObject theObject, String nextName, String thePosition) {
1254                BaseJsonLikeValue object = theObject.get(nextName);
1255                if (object == null || object.isNull()) {
1256                        return null;
1257                }
1258                if (!object.isArray()) {
1259                        throw new DataFormatException(
1260                                        Msg.code(1841) + "Syntax error parsing JSON FHIR structure: Expected ARRAY at element '"
1261                                                        + thePosition + "', found '" + object.getJsonType() + "'");
1262                }
1263                return object.getAsArray();
1264        }
1265
1266        private void parseAlternates(
1267                        BaseJsonLikeValue theAlternateVal,
1268                        ParserState<?> theState,
1269                        String theElementName,
1270                        String theAlternateName) {
1271                if (theAlternateVal == null || theAlternateVal.isNull()) {
1272                        return;
1273                }
1274
1275                if (theAlternateVal.isArray()) {
1276                        BaseJsonLikeArray array = theAlternateVal.getAsArray();
1277                        if (array.size() > 1) {
1278                                throw new DataFormatException(Msg.code(1842) + "Unexpected array of length " + array.size()
1279                                                + " (expected 0 or 1) for element: " + theElementName);
1280                        }
1281                        if (array.size() == 0) {
1282                                return;
1283                        }
1284                        parseAlternates(array.get(0), theState, theElementName, theAlternateName);
1285                        return;
1286                }
1287
1288                BaseJsonLikeValue alternateVal = theAlternateVal;
1289                if (alternateVal.isObject() == false) {
1290                        getErrorHandler()
1291                                        .incorrectJsonType(
1292                                                        null, theAlternateName, ValueType.OBJECT, null, alternateVal.getJsonType(), null);
1293                        return;
1294                }
1295
1296                BaseJsonLikeObject alternate = alternateVal.getAsObject();
1297
1298                for (Iterator<String> keyIter = alternate.keyIterator(); keyIter.hasNext(); ) {
1299                        String nextKey = keyIter.next();
1300                        BaseJsonLikeValue nextVal = alternate.get(nextKey);
1301                        if ("extension".equals(nextKey)) {
1302                                boolean isModifier = false;
1303                                BaseJsonLikeArray array = nextVal.getAsArray();
1304                                parseExtension(theState, array, isModifier);
1305                        } else if ("modifierExtension".equals(nextKey)) {
1306                                boolean isModifier = true;
1307                                BaseJsonLikeArray array = nextVal.getAsArray();
1308                                parseExtension(theState, array, isModifier);
1309                        } else if ("id".equals(nextKey)) {
1310                                if (nextVal.isString()) {
1311                                        theState.attributeValue("id", nextVal.getAsString());
1312                                } else {
1313                                        getErrorHandler()
1314                                                        .incorrectJsonType(
1315                                                                        null,
1316                                                                        "id",
1317                                                                        ValueType.SCALAR,
1318                                                                        ScalarType.STRING,
1319                                                                        nextVal.getJsonType(),
1320                                                                        nextVal.getDataType());
1321                                }
1322                        } else if ("fhir_comments".equals(nextKey)) {
1323                                parseFhirComments(nextVal, theState);
1324                        }
1325                }
1326        }
1327
1328        private void parseChildren(BaseJsonLikeObject theObject, ParserState<?> theState) {
1329                int allUnderscoreNames = 0;
1330                int handledUnderscoreNames = 0;
1331
1332                for (Iterator<String> keyIter = theObject.keyIterator(); keyIter.hasNext(); ) {
1333                        String nextName = keyIter.next();
1334                        if ("resourceType".equals(nextName)) {
1335                                if (theState.isToplevelResourceElement()) {
1336                                        continue;
1337                                }
1338                        } else if ("extension".equals(nextName)) {
1339                                BaseJsonLikeArray array = grabJsonArray(theObject, nextName, "extension");
1340                                parseExtension(theState, array, false);
1341                                continue;
1342                        } else if ("modifierExtension".equals(nextName)) {
1343                                BaseJsonLikeArray array = grabJsonArray(theObject, nextName, "modifierExtension");
1344                                parseExtension(theState, array, true);
1345                                continue;
1346                        } else if (nextName.equals("fhir_comments")) {
1347                                parseFhirComments(theObject.get(nextName), theState);
1348                                continue;
1349                        } else if (nextName.charAt(0) == '_') {
1350                                allUnderscoreNames++;
1351                                continue;
1352                        }
1353
1354                        BaseJsonLikeValue nextVal = theObject.get(nextName);
1355                        String alternateName = '_' + nextName;
1356                        BaseJsonLikeValue alternateVal = theObject.get(alternateName);
1357                        if (alternateVal != null) {
1358                                handledUnderscoreNames++;
1359                        }
1360
1361                        parseChildren(theState, nextName, nextVal, alternateVal, alternateName, false);
1362                }
1363
1364                // if (elementId != null) {
1365                // IBase object = (IBase) theState.getObject();
1366                // if (object instanceof IIdentifiableElement) {
1367                // ((IIdentifiableElement) object).setElementSpecificId(elementId);
1368                // } else if (object instanceof IBaseResource) {
1369                // ((IBaseResource) object).getIdElement().setValue(elementId);
1370                // }
1371                // }
1372
1373                /*
1374                 * This happens if an element has an extension but no actual value. I.e.
1375                 * if a resource has a "_status" element but no corresponding "status"
1376                 * element. This could be used to handle a null value with an extension
1377                 * for example.
1378                 */
1379                if (allUnderscoreNames > handledUnderscoreNames) {
1380                        for (Iterator<String> keyIter = theObject.keyIterator(); keyIter.hasNext(); ) {
1381                                String alternateName = keyIter.next();
1382                                if (alternateName.startsWith("_") && alternateName.length() > 1) {
1383                                        BaseJsonLikeValue nextValue = theObject.get(alternateName);
1384                                        String nextName = alternateName.substring(1);
1385
1386                                        if (nextValue != null) {
1387                                                BaseJsonLikeValue nonAlternativeValue = theObject.get(nextName);
1388
1389                                                // Only alternate values with no corresponding "normal" value is unhandled from previous step.
1390                                                if (nonAlternativeValue != null) {
1391                                                        continue;
1392                                                }
1393
1394                                                if (nextValue.isObject()) {
1395                                                        if (theObject.get(nextName) == null) {
1396                                                                theState.enteringNewElement(null, nextName);
1397                                                                parseAlternates(nextValue, theState, alternateName, alternateName);
1398                                                                theState.endingElement();
1399                                                        }
1400                                                } else {
1401                                                        getErrorHandler()
1402                                                                        .incorrectJsonType(
1403                                                                                        null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
1404                                                }
1405                                        }
1406                                }
1407                        }
1408                }
1409        }
1410
1411        private void parseChildren(
1412                        ParserState<?> theState,
1413                        String theName,
1414                        BaseJsonLikeValue theJsonVal,
1415                        BaseJsonLikeValue theAlternateVal,
1416                        String theAlternateName,
1417                        boolean theInArray) {
1418                if (theName.equals("id")) {
1419                        if (!theJsonVal.isString()) {
1420                                getErrorHandler()
1421                                                .incorrectJsonType(
1422                                                                null,
1423                                                                "id",
1424                                                                ValueType.SCALAR,
1425                                                                ScalarType.STRING,
1426                                                                theJsonVal.getJsonType(),
1427                                                                theJsonVal.getDataType());
1428                        }
1429                }
1430
1431                if (theJsonVal.isArray()) {
1432                        BaseJsonLikeArray nextArray = theJsonVal.getAsArray();
1433
1434                        BaseJsonLikeValue alternateVal = theAlternateVal;
1435                        if (alternateVal != null && alternateVal.isArray() == false) {
1436                                getErrorHandler()
1437                                                .incorrectJsonType(
1438                                                                null, theAlternateName, ValueType.ARRAY, null, alternateVal.getJsonType(), null);
1439                                alternateVal = null;
1440                        }
1441
1442                        BaseJsonLikeArray nextAlternateArray = BaseJsonLikeValue.asArray(alternateVal); // could be null
1443                        for (int i = 0; i < nextArray.size(); i++) {
1444                                BaseJsonLikeValue nextObject = nextArray.get(i);
1445                                BaseJsonLikeValue nextAlternate = null;
1446                                if (nextAlternateArray != null && nextAlternateArray.size() >= (i + 1)) {
1447                                        nextAlternate = nextAlternateArray.get(i);
1448                                }
1449                                parseChildren(theState, theName, nextObject, nextAlternate, theAlternateName, true);
1450                        }
1451                } else if (theJsonVal.isObject()) {
1452                        if (!theInArray && theState.elementIsRepeating(theName)) {
1453                                getErrorHandler().incorrectJsonType(null, theName, ValueType.ARRAY, null, ValueType.OBJECT, null);
1454                        }
1455
1456                        theState.enteringNewElement(null, theName);
1457                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1458                        BaseJsonLikeObject nextObject = theJsonVal.getAsObject();
1459                        boolean preResource = false;
1460                        if (theState.isPreResource()) {
1461                                BaseJsonLikeValue resType = nextObject.get("resourceType");
1462                                if (resType == null || !resType.isString()) {
1463                                        throw new DataFormatException(Msg.code(1843)
1464                                                        + "Missing required element 'resourceType' from JSON resource object, unable to parse");
1465                                }
1466                                theState.enteringNewElement(null, resType.getAsString());
1467                                preResource = true;
1468                        }
1469                        parseChildren(nextObject, theState);
1470                        if (preResource) {
1471                                theState.endingElement();
1472                        }
1473                        theState.endingElement();
1474                } else if (theJsonVal.isNull()) {
1475                        theState.enteringNewElement(null, theName);
1476                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1477                        theState.endingElement();
1478                } else {
1479                        // must be a SCALAR
1480                        theState.enteringNewElement(null, theName);
1481                        String asString = theJsonVal.getAsString();
1482                        theState.attributeValue("value", asString);
1483                        parseAlternates(theAlternateVal, theState, theAlternateName, theAlternateName);
1484                        theState.endingElement();
1485                }
1486        }
1487
1488        private void parseExtension(ParserState<?> theState, BaseJsonLikeArray theValues, boolean theIsModifier) {
1489                int allUnderscoreNames = 0;
1490                int handledUnderscoreNames = 0;
1491
1492                if (theValues == null) {
1493                        String parentElementName = getExtensionElementName(theIsModifier);
1494                        getErrorHandler()
1495                                        .missingRequiredElement(new ParseLocation().setParentElementName(parentElementName), "url");
1496                        return;
1497                }
1498
1499                for (int i = 0; i < theValues.size(); i++) {
1500                        BaseJsonLikeObject nextExtObj = BaseJsonLikeValue.asObject(theValues.get(i));
1501                        BaseJsonLikeValue jsonElement = nextExtObj.get("url");
1502                        String url;
1503                        if (null == jsonElement || !(jsonElement.isScalar())) {
1504                                String parentElementName = getExtensionElementName(theIsModifier);
1505                                getErrorHandler()
1506                                                .missingRequiredElement(new ParseLocation().setParentElementName(parentElementName), "url");
1507                                url = null;
1508                        } else {
1509                                url = getExtensionUrl(jsonElement.getAsString());
1510                        }
1511                        theState.enteringNewElementExtension(null, url, theIsModifier, getServerBaseUrl());
1512                        for (Iterator<String> keyIter = nextExtObj.keyIterator(); keyIter.hasNext(); ) {
1513                                String next = keyIter.next();
1514                                if ("url".equals(next)) {
1515                                        continue;
1516                                } else if ("extension".equals(next)) {
1517                                        BaseJsonLikeArray jsonVal = BaseJsonLikeValue.asArray(nextExtObj.get(next));
1518                                        parseExtension(theState, jsonVal, false);
1519                                } else if ("modifierExtension".equals(next)) {
1520                                        BaseJsonLikeArray jsonVal = BaseJsonLikeValue.asArray(nextExtObj.get(next));
1521                                        parseExtension(theState, jsonVal, true);
1522                                } else if (next.charAt(0) == '_') {
1523                                        allUnderscoreNames++;
1524                                        continue;
1525                                } else {
1526                                        BaseJsonLikeValue jsonVal = nextExtObj.get(next);
1527                                        String alternateName = '_' + next;
1528                                        BaseJsonLikeValue alternateVal = nextExtObj.get(alternateName);
1529                                        if (alternateVal != null) {
1530                                                handledUnderscoreNames++;
1531                                        }
1532                                        parseChildren(theState, next, jsonVal, alternateVal, alternateName, false);
1533                                }
1534                        }
1535
1536                        /*
1537                         * This happens if an element has an extension but no actual value. I.e.
1538                         * if a resource has a "_status" element but no corresponding "status"
1539                         * element. This could be used to handle a null value with an extension
1540                         * for example.
1541                         */
1542                        if (allUnderscoreNames > handledUnderscoreNames) {
1543                                for (Iterator<String> keyIter = nextExtObj.keyIterator(); keyIter.hasNext(); ) {
1544                                        String alternateName = keyIter.next();
1545                                        if (alternateName.startsWith("_") && alternateName.length() > 1) {
1546                                                BaseJsonLikeValue nextValue = nextExtObj.get(alternateName);
1547                                                if (nextValue != null) {
1548                                                        if (nextValue.isObject()) {
1549                                                                String nextName = alternateName.substring(1);
1550                                                                if (nextExtObj.get(nextName) == null) {
1551                                                                        theState.enteringNewElement(null, nextName);
1552                                                                        parseAlternates(nextValue, theState, alternateName, alternateName);
1553                                                                        theState.endingElement();
1554                                                                }
1555                                                        } else {
1556                                                                getErrorHandler()
1557                                                                                .incorrectJsonType(
1558                                                                                                null,
1559                                                                                                alternateName,
1560                                                                                                ValueType.OBJECT,
1561                                                                                                null,
1562                                                                                                nextValue.getJsonType(),
1563                                                                                                null);
1564                                                        }
1565                                                }
1566                                        }
1567                                }
1568                        }
1569                        theState.endingElement();
1570                }
1571        }
1572
1573        @Nonnull
1574        private static String getExtensionElementName(boolean theIsModifier) {
1575                String parentElementName;
1576                if (theIsModifier) {
1577                        parentElementName = "modifierExtension";
1578                } else {
1579                        parentElementName = "extension";
1580                }
1581                return parentElementName;
1582        }
1583
1584        private void parseFhirComments(BaseJsonLikeValue theObject, ParserState<?> theState) {
1585                if (isSupportsFhirComment()) {
1586                        if (theObject.isArray()) {
1587                                BaseJsonLikeArray comments = theObject.getAsArray();
1588                                for (int i = 0; i < comments.size(); i++) {
1589                                        BaseJsonLikeValue nextComment = comments.get(i);
1590                                        if (nextComment.isString()) {
1591                                                String commentText = nextComment.getAsString();
1592                                                if (commentText != null) {
1593                                                        theState.commentPre(commentText);
1594                                                }
1595                                        }
1596                                }
1597                        }
1598                }
1599        }
1600
1601        @Override
1602        public <T extends IBaseResource> T parseResource(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure)
1603                        throws DataFormatException {
1604
1605                /*****************************************************
1606                 * ************************************************* *
1607                 * ** NOTE: this duplicates most of the code in ** *
1608                 * ** BaseParser.parseResource(Class<T>, Reader). ** *
1609                 * ** Unfortunately, there is no way to avoid ** *
1610                 * ** this without doing some refactoring of the ** *
1611                 * ** BaseParser class. ** *
1612                 * ************************************************* *
1613                 *****************************************************/
1614
1615                /*
1616                 * We do this so that the context can verify that the structure is for
1617                 * the correct FHIR version
1618                 */
1619                if (theResourceType != null) {
1620                        getContext().getResourceDefinition(theResourceType);
1621                }
1622
1623                // Actually do the parse
1624                T retVal = doParseResource(theResourceType, theJsonLikeStructure);
1625
1626                RuntimeResourceDefinition def = getContext().getResourceDefinition(retVal);
1627                if ("Bundle".equals(def.getName())) {
1628
1629                        BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
1630                        BaseRuntimeElementCompositeDefinition<?> entryDef =
1631                                        (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
1632                        List<IBase> entries = entryChild.getAccessor().getValues(retVal);
1633                        if (entries != null) {
1634                                for (IBase nextEntry : entries) {
1635
1636                                        /**
1637                                         * If Bundle.entry.fullUrl is populated, set the resource ID to that
1638                                         */
1639                                        // TODO: should emit a warning and maybe notify the error handler if the resource ID doesn't match
1640                                        // the
1641                                        // fullUrl idPart
1642                                        BaseRuntimeChildDefinition fullUrlChild = entryDef.getChildByName("fullUrl");
1643                                        if (fullUrlChild == null) {
1644                                                continue; // TODO: remove this once the data model in tinder plugin catches up to 1.2
1645                                        }
1646                                        List<IBase> fullUrl = fullUrlChild.getAccessor().getValues(nextEntry);
1647                                        if (fullUrl != null && !fullUrl.isEmpty()) {
1648                                                IPrimitiveType<?> value = (IPrimitiveType<?>) fullUrl.get(0);
1649                                                if (value.isEmpty() == false) {
1650                                                        List<IBase> entryResources = entryDef.getChildByName("resource")
1651                                                                        .getAccessor()
1652                                                                        .getValues(nextEntry);
1653                                                        if (entryResources != null && entryResources.size() > 0) {
1654                                                                IBaseResource res = (IBaseResource) entryResources.get(0);
1655                                                                String versionId = res.getIdElement().getVersionIdPart();
1656                                                                res.setId(value.getValueAsString());
1657                                                                if (isNotBlank(versionId) && res.getIdElement().hasVersionIdPart() == false) {
1658                                                                        res.setId(res.getIdElement().withVersion(versionId));
1659                                                                }
1660                                                        }
1661                                                }
1662                                        }
1663                                }
1664                        }
1665                }
1666
1667                return retVal;
1668        }
1669
1670        @Override
1671        public IBaseResource parseResource(JsonLikeStructure theJsonLikeStructure) throws DataFormatException {
1672                return parseResource(null, theJsonLikeStructure);
1673        }
1674
1675        @Override
1676        public IParser setPrettyPrint(boolean thePrettyPrint) {
1677                myPrettyPrint = thePrettyPrint;
1678                return this;
1679        }
1680
1681        private void write(BaseJsonLikeWriter theEventWriter, String theChildName, Boolean theValue) throws IOException {
1682                if (theValue != null) {
1683                        theEventWriter.write(theChildName, theValue.booleanValue());
1684                }
1685        }
1686
1687        // private void parseExtensionInDstu2Style(boolean theModifier, ParserState<?> theState, String
1688        // theParentExtensionUrl, String theExtensionUrl, JsonArray theValues) {
1689        // String extUrl = UrlUtil.constructAbsoluteUrl(theParentExtensionUrl, theExtensionUrl);
1690        // theState.enteringNewElementExtension(null, extUrl, theModifier);
1691        //
1692        // for (int extIdx = 0; extIdx < theValues.size(); extIdx++) {
1693        // JsonObject nextExt = theValues.getJsonObject(extIdx);
1694        // for (String nextKey : nextExt.keySet()) {
1695        // // if (nextKey.startsWith("value") && nextKey.length() > 5 &&
1696        // // myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(nextKey) != null) {
1697        // JsonElement jsonVal = nextExt.get(nextKey);
1698        // if (jsonVal.getValueType() == ValueType.ARRAY) {
1699        // /*
1700        // * Extension children which are arrays are sub-extensions. Any other value type should be treated as a value.
1701        // */
1702        // JsonArray arrayValue = (JsonArray) jsonVal;
1703        // parseExtensionInDstu2Style(theModifier, theState, extUrl, nextKey, arrayValue);
1704        // } else {
1705        // parseChildren(theState, nextKey, jsonVal, null, null);
1706        // }
1707        // }
1708        // }
1709        //
1710        // theState.endingElement();
1711        // }
1712
1713        private void write(BaseJsonLikeWriter theEventWriter, String theChildName, BigDecimal theDecimalValue)
1714                        throws IOException {
1715                theEventWriter.write(theChildName, theDecimalValue);
1716        }
1717
1718        private void write(BaseJsonLikeWriter theEventWriter, String theChildName, Integer theValue) throws IOException {
1719                theEventWriter.write(theChildName, theValue);
1720        }
1721
1722        private void writeCommentsPreAndPost(IBase theNextValue, BaseJsonLikeWriter theEventWriter) throws IOException {
1723                if (theNextValue.hasFormatComment()) {
1724                        if (isSupportsFhirComment()) {
1725                                beginArray(theEventWriter, "fhir_comments");
1726                                List<String> pre = theNextValue.getFormatCommentsPre();
1727                                if (pre.isEmpty() == false) {
1728                                        for (String next : pre) {
1729                                                theEventWriter.write(next);
1730                                        }
1731                                }
1732                                List<String> post = theNextValue.getFormatCommentsPost();
1733                                if (post.isEmpty() == false) {
1734                                        for (String next : post) {
1735                                                theEventWriter.write(next);
1736                                        }
1737                                }
1738                                theEventWriter.endArray();
1739                        }
1740                }
1741        }
1742
1743        private void writeExtensionsAsDirectChild(
1744                        IBaseResource theResource,
1745                        BaseJsonLikeWriter theEventWriter,
1746                        RuntimeResourceDefinition resDef,
1747                        List<HeldExtension> extensions,
1748                        List<HeldExtension> modifierExtensions,
1749                        EncodeContext theEncodeContext,
1750                        boolean theContainedResource)
1751                        throws IOException {
1752                // Write Extensions
1753                if (extensions.isEmpty() == false) {
1754                        theEncodeContext.pushPath("extension", false);
1755                        beginArray(theEventWriter, "extension");
1756                        for (HeldExtension next : extensions) {
1757                                next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource);
1758                        }
1759                        theEventWriter.endArray();
1760                        theEncodeContext.popPath();
1761                }
1762
1763                // Write ModifierExtensions
1764                if (modifierExtensions.isEmpty() == false) {
1765                        theEncodeContext.pushPath("modifierExtension", false);
1766                        beginArray(theEventWriter, "modifierExtension");
1767                        for (HeldExtension next : modifierExtensions) {
1768                                next.write(resDef, theResource, theEventWriter, theEncodeContext, theContainedResource);
1769                        }
1770                        theEventWriter.endArray();
1771                        theEncodeContext.popPath();
1772                }
1773        }
1774
1775        private void writeOptionalTagWithTextNode(
1776                        BaseJsonLikeWriter theEventWriter, String theElementName, IPrimitiveDatatype<?> thePrimitive)
1777                        throws IOException {
1778                if (thePrimitive == null) {
1779                        return;
1780                }
1781                String str = thePrimitive.getValueAsString();
1782                writeOptionalTagWithTextNode(theEventWriter, theElementName, str);
1783        }
1784
1785        private void writeOptionalTagWithTextNode(BaseJsonLikeWriter theEventWriter, String theElementName, String theValue)
1786                        throws IOException {
1787                if (StringUtils.isNotBlank(theValue)) {
1788                        write(theEventWriter, theElementName, theValue);
1789                }
1790        }
1791
1792        private static void write(BaseJsonLikeWriter theWriter, String theName, String theValue) throws IOException {
1793                theWriter.write(theName, theValue);
1794        }
1795
1796        private class HeldExtension implements Comparable<HeldExtension> {
1797
1798                private CompositeChildElement myChildElem;
1799                private RuntimeChildDeclaredExtensionDefinition myDef;
1800                private boolean myModifier;
1801                private IBaseExtension<?, ?> myUndeclaredExtension;
1802                private IBase myValue;
1803                private CompositeChildElement myParent;
1804
1805                public HeldExtension(
1806                                IBaseExtension<?, ?> theUndeclaredExtension,
1807                                boolean theModifier,
1808                                CompositeChildElement theChildElem,
1809                                CompositeChildElement theParent) {
1810                        assert theUndeclaredExtension != null;
1811                        myUndeclaredExtension = theUndeclaredExtension;
1812                        myModifier = theModifier;
1813                        myChildElem = theChildElem;
1814                        myParent = theParent;
1815                }
1816
1817                public HeldExtension(
1818                                RuntimeChildDeclaredExtensionDefinition theDef, IBase theValue, CompositeChildElement theChildElem) {
1819                        assert theDef != null;
1820                        assert theValue != null;
1821                        myDef = theDef;
1822                        myValue = theValue;
1823                        myChildElem = theChildElem;
1824                }
1825
1826                @Override
1827                public int compareTo(HeldExtension theArg0) {
1828                        String url1 = myDef != null ? myDef.getExtensionUrl() : myUndeclaredExtension.getUrl();
1829                        String url2 =
1830                                        theArg0.myDef != null ? theArg0.myDef.getExtensionUrl() : theArg0.myUndeclaredExtension.getUrl();
1831                        url1 = defaultString(getExtensionUrl(url1));
1832                        url2 = defaultString(getExtensionUrl(url2));
1833                        return url1.compareTo(url2);
1834                }
1835
1836                private void managePrimitiveExtension(
1837                                final IBase theValue,
1838                                final RuntimeResourceDefinition theResDef,
1839                                final IBaseResource theResource,
1840                                final BaseJsonLikeWriter theEventWriter,
1841                                final BaseRuntimeElementDefinition<?> def,
1842                                final String childName,
1843                                EncodeContext theEncodeContext,
1844                                boolean theContainedResource)
1845                                throws IOException {
1846                        if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
1847                                final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
1848                                final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
1849                                // Undeclared extensions
1850                                extractUndeclaredExtensions(
1851                                                theValue,
1852                                                extensions,
1853                                                modifierExtensions,
1854                                                myParent,
1855                                                null,
1856                                                theEncodeContext,
1857                                                theContainedResource);
1858                                // Declared extensions
1859                                extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent);
1860                                boolean haveContent = false;
1861                                if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) {
1862                                        haveContent = true;
1863                                }
1864                                if (haveContent) {
1865                                        beginObject(theEventWriter, '_' + childName);
1866                                        writeExtensionsAsDirectChild(
1867                                                        theResource,
1868                                                        theEventWriter,
1869                                                        theResDef,
1870                                                        extensions,
1871                                                        modifierExtensions,
1872                                                        theEncodeContext,
1873                                                        theContainedResource);
1874                                        theEventWriter.endObject();
1875                                }
1876                        }
1877                }
1878
1879                public void write(
1880                                RuntimeResourceDefinition theResDef,
1881                                IBaseResource theResource,
1882                                BaseJsonLikeWriter theEventWriter,
1883                                EncodeContext theEncodeContext,
1884                                boolean theContainedResource)
1885                                throws IOException {
1886                        if (myUndeclaredExtension != null) {
1887                                writeUndeclaredExtension(
1888                                                theResDef,
1889                                                theResource,
1890                                                theEventWriter,
1891                                                myUndeclaredExtension,
1892                                                theEncodeContext,
1893                                                theContainedResource);
1894                        } else {
1895                                theEventWriter.beginObject();
1896
1897                                writeCommentsPreAndPost(myValue, theEventWriter);
1898
1899                                JsonParser.write(theEventWriter, "url", getExtensionUrl(myDef.getExtensionUrl()));
1900
1901                                /*
1902                                 * This makes sure that even if the extension contains a reference to a contained
1903                                 * resource which has a HAPI-assigned ID we'll still encode that ID.
1904                                 *
1905                                 * See #327
1906                                 */
1907                                List<? extends IBase> preProcessedValue = preProcessValues(
1908                                                myDef, theResource, Collections.singletonList(myValue), myChildElem, theEncodeContext);
1909
1910                                // // Check for undeclared extensions on the declared extension
1911                                // // (grrrrrr....)
1912                                // if (myValue instanceof ISupportsUndeclaredExtensions) {
1913                                // ISupportsUndeclaredExtensions value = (ISupportsUndeclaredExtensions)myValue;
1914                                // List<ExtensionDt> exts = value.getUndeclaredExtensions();
1915                                // if (exts.size() > 0) {
1916                                // ArrayList<IBase> newValueList = new ArrayList<IBase>();
1917                                // newValueList.addAll(preProcessedValue);
1918                                // newValueList.addAll(exts);
1919                                // preProcessedValue = newValueList;
1920                                // }
1921                                // }
1922
1923                                myValue = preProcessedValue.get(0);
1924
1925                                BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
1926                                if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
1927                                        extractAndWriteExtensionsAsDirectChild(
1928                                                        myValue,
1929                                                        theEventWriter,
1930                                                        def,
1931                                                        theResDef,
1932                                                        theResource,
1933                                                        myChildElem,
1934                                                        null,
1935                                                        theEncodeContext,
1936                                                        theContainedResource);
1937                                } else {
1938                                        String childName = myDef.getChildNameByDatatype(myValue.getClass());
1939                                        encodeChildElementToStreamWriter(
1940                                                        theResDef,
1941                                                        theResource,
1942                                                        theEventWriter,
1943                                                        myValue,
1944                                                        def,
1945                                                        childName,
1946                                                        false,
1947                                                        myParent,
1948                                                        false,
1949                                                        theEncodeContext);
1950                                        managePrimitiveExtension(
1951                                                        myValue,
1952                                                        theResDef,
1953                                                        theResource,
1954                                                        theEventWriter,
1955                                                        def,
1956                                                        childName,
1957                                                        theEncodeContext,
1958                                                        theContainedResource);
1959                                }
1960
1961                                theEventWriter.endObject();
1962                        }
1963                }
1964
1965                private void writeUndeclaredExtension(
1966                                RuntimeResourceDefinition theResDef,
1967                                IBaseResource theResource,
1968                                BaseJsonLikeWriter theEventWriter,
1969                                IBaseExtension<?, ?> ext,
1970                                EncodeContext theEncodeContext,
1971                                boolean theContainedResource)
1972                                throws IOException {
1973                        IBase value = ext.getValue();
1974                        final String extensionUrl = getExtensionUrl(ext.getUrl());
1975
1976                        theEventWriter.beginObject();
1977
1978                        writeCommentsPreAndPost(myUndeclaredExtension, theEventWriter);
1979
1980                        String elementId = getCompositeElementId(ext);
1981                        if (isNotBlank(elementId)) {
1982                                JsonParser.write(theEventWriter, "id", getCompositeElementId(ext));
1983                        }
1984
1985                        if (isBlank(extensionUrl)) {
1986                                ParseLocation loc = new ParseLocation(theEncodeContext.toString());
1987                                getErrorHandler().missingRequiredElement(loc, "url");
1988                        }
1989
1990                        JsonParser.write(theEventWriter, "url", extensionUrl);
1991
1992                        boolean noValue = value == null || value.isEmpty();
1993                        if (noValue && ext.getExtension().isEmpty()) {
1994
1995                                ParseLocation loc = new ParseLocation(theEncodeContext.toString());
1996                                getErrorHandler().missingRequiredElement(loc, "value");
1997                                ourLog.debug("Extension with URL[{}] has no value", extensionUrl);
1998
1999                        } else {
2000
2001                                if (!noValue && !ext.getExtension().isEmpty()) {
2002                                        ParseLocation loc = new ParseLocation(theEncodeContext.toString());
2003                                        getErrorHandler().extensionContainsValueAndNestedExtensions(loc);
2004                                }
2005
2006                                // Write child extensions
2007                                if (!ext.getExtension().isEmpty()) {
2008
2009                                        if (myModifier) {
2010                                                beginArray(theEventWriter, "modifierExtension");
2011                                        } else {
2012                                                beginArray(theEventWriter, "extension");
2013                                        }
2014
2015                                        for (Object next : ext.getExtension()) {
2016                                                writeUndeclaredExtension(
2017                                                                theResDef,
2018                                                                theResource,
2019                                                                theEventWriter,
2020                                                                (IBaseExtension<?, ?>) next,
2021                                                                theEncodeContext,
2022                                                                theContainedResource);
2023                                        }
2024                                        theEventWriter.endArray();
2025                                }
2026
2027                                // Write value
2028                                if (!noValue) {
2029                                        theEncodeContext.pushPath("value", false);
2030
2031                                        /*
2032                                         * Pre-process value - This is called in case the value is a reference
2033                                         * since we might modify the text
2034                                         */
2035                                        value = preProcessValues(
2036                                                                        myDef, theResource, Collections.singletonList(value), myChildElem, theEncodeContext)
2037                                                        .get(0);
2038
2039                                        RuntimeChildUndeclaredExtensionDefinition extDef =
2040                                                        getContext().getRuntimeChildUndeclaredExtensionDefinition();
2041                                        String childName = extDef.getChildNameByDatatype(value.getClass());
2042                                        if (childName == null) {
2043                                                childName = "value"
2044                                                                + WordUtils.capitalize(getContext()
2045                                                                                .getElementDefinition(value.getClass())
2046                                                                                .getName());
2047                                        }
2048                                        BaseRuntimeElementDefinition<?> childDef =
2049                                                        extDef.getChildElementDefinitionByDatatype(value.getClass());
2050                                        if (childDef == null) {
2051                                                throw new ConfigurationException(
2052                                                                Msg.code(1844) + "Unable to encode extension, unrecognized child element type: "
2053                                                                                + value.getClass().getCanonicalName());
2054                                        }
2055                                        encodeChildElementToStreamWriter(
2056                                                        theResDef,
2057                                                        theResource,
2058                                                        theEventWriter,
2059                                                        value,
2060                                                        childDef,
2061                                                        childName,
2062                                                        false,
2063                                                        myParent,
2064                                                        false,
2065                                                        theEncodeContext);
2066                                        managePrimitiveExtension(
2067                                                        value,
2068                                                        theResDef,
2069                                                        theResource,
2070                                                        theEventWriter,
2071                                                        childDef,
2072                                                        childName,
2073                                                        theEncodeContext,
2074                                                        theContainedResource);
2075
2076                                        theEncodeContext.popPath();
2077                                }
2078                        }
2079
2080                        // theEventWriter.name(myUndeclaredExtension.get);
2081
2082                        theEventWriter.endObject();
2083                }
2084        }
2085}