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