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