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