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