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