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