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