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