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