View Javadoc
1   package ca.uhn.fhir.parser;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import static org.apache.commons.lang3.StringUtils.*;
24  
25  import java.io.IOException;
26  import java.io.Reader;
27  import java.io.StringReader;
28  import java.io.Writer;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Map.Entry;
37  import java.util.Set;
38  
39  import javax.json.Json;
40  import javax.json.JsonArray;
41  import javax.json.JsonObject;
42  import javax.json.JsonReader;
43  import javax.json.JsonString;
44  import javax.json.JsonValue;
45  import javax.json.JsonValue.ValueType;
46  import javax.json.stream.JsonGenerator;
47  import javax.json.stream.JsonGeneratorFactory;
48  import javax.json.stream.JsonParsingException;
49  
50  import org.apache.commons.lang3.StringUtils;
51  import org.apache.commons.lang3.Validate;
52  
53  import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
54  import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
55  import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
56  import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
57  import ca.uhn.fhir.context.ConfigurationException;
58  import ca.uhn.fhir.context.FhirContext;
59  import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
60  import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
61  import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
62  import ca.uhn.fhir.context.RuntimeResourceDefinition;
63  import ca.uhn.fhir.model.api.BaseBundle;
64  import ca.uhn.fhir.model.api.Bundle;
65  import ca.uhn.fhir.model.api.BundleEntry;
66  import ca.uhn.fhir.model.api.ExtensionDt;
67  import ca.uhn.fhir.model.api.IElement;
68  import ca.uhn.fhir.model.api.IIdentifiableElement;
69  import ca.uhn.fhir.model.api.IPrimitiveDatatype;
70  import ca.uhn.fhir.model.api.IResource;
71  import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
72  import ca.uhn.fhir.model.api.Tag;
73  import ca.uhn.fhir.model.api.TagList;
74  import ca.uhn.fhir.model.api.annotation.Child;
75  import ca.uhn.fhir.model.dstu.composite.ContainedDt;
76  import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
77  import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
78  import ca.uhn.fhir.model.dstu.resource.Binary;
79  import ca.uhn.fhir.model.primitive.BooleanDt;
80  import ca.uhn.fhir.model.primitive.DecimalDt;
81  import ca.uhn.fhir.model.primitive.IdDt;
82  import ca.uhn.fhir.model.primitive.IntegerDt;
83  import ca.uhn.fhir.model.primitive.StringDt;
84  import ca.uhn.fhir.model.primitive.XhtmlDt;
85  import ca.uhn.fhir.narrative.INarrativeGenerator;
86  
87  public class JsonParser extends BaseParser implements IParser {
88  
89  	private static final Set<String> BUNDLE_TEXTNODE_CHILDREN;
90  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParser.HeldExtension.class);
91  
92  	static {
93  		HashSet<String> hashSet = new HashSet<String>();
94  		hashSet.add("title");
95  		hashSet.add("id");
96  		hashSet.add("updated");
97  		hashSet.add("published");
98  		BUNDLE_TEXTNODE_CHILDREN = Collections.unmodifiableSet(hashSet);
99  	}
100 
101 	private FhirContext myContext;
102 
103 	private boolean myPrettyPrint;
104 
105 	public JsonParser(FhirContext theContext) {
106 		super(theContext);
107 		myContext = theContext;
108 	}
109 
110 	private void addToHeldExtensions(int valueIdx, List<ExtensionDt> ext, ArrayList<ArrayList<HeldExtension>> list) {
111 		if (ext.size() > 0) {
112 			list.ensureCapacity(valueIdx);
113 			while (list.size() <= valueIdx) {
114 				list.add(null);
115 			}
116 			if (list.get(valueIdx) == null) {
117 				list.set(valueIdx, new ArrayList<JsonParser.HeldExtension>());
118 			}
119 			for (ExtensionDt next : ext) {
120 				list.get(valueIdx).add(new HeldExtension(next));
121 			}
122 		}
123 	}
124 
125 	private void assertObjectOfType(JsonValue theResourceTypeObj, ValueType theValueType, String thePosition) {
126 		if (theResourceTypeObj == null) {
127 			throw new DataFormatException("Invalid JSON content detected, missing required element: '" + thePosition + "'");
128 		}
129 		
130 		if (theResourceTypeObj.getValueType() != theValueType) {
131 			throw new DataFormatException("Invalid content of element " + thePosition + ", expected " + theValueType);
132 		}
133 	}
134 
135 	private JsonGenerator createJsonGenerator(Writer theWriter) {
136 		Map<String, Object> properties = new HashMap<String, Object>(1);
137 		if (myPrettyPrint) {
138 			properties.put(JsonGenerator.PRETTY_PRINTING, myPrettyPrint);
139 		}
140 		JsonGeneratorFactory jgf = Json.createGeneratorFactory(properties);
141 		JsonGenerator eventWriter = jgf.createGenerator(theWriter);
142 		return eventWriter;
143 	}
144 
145 	@Override
146 	public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException {
147 		JsonGenerator eventWriter = createJsonGenerator(theWriter);
148 		eventWriter.writeStartObject();
149 
150 		eventWriter.write("resourceType", "Bundle");
151 
152 		writeTagWithTextNode(eventWriter, "title", theBundle.getTitle());
153 		writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId());
154 		writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated());
155 		writeOptionalTagWithTextNode(eventWriter, "published", theBundle.getPublished());
156 
157 		boolean linkStarted = false;
158 		linkStarted = writeAtomLink(eventWriter, "self", theBundle.getLinkSelf(), linkStarted);
159 		linkStarted = writeAtomLink(eventWriter, "first", theBundle.getLinkFirst(), linkStarted);
160 		linkStarted = writeAtomLink(eventWriter, "previous", theBundle.getLinkPrevious(), linkStarted);
161 		linkStarted = writeAtomLink(eventWriter, "next", theBundle.getLinkNext(), linkStarted);
162 		linkStarted = writeAtomLink(eventWriter, "last", theBundle.getLinkLast(), linkStarted);
163 		linkStarted = writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase(), linkStarted);
164 		if (linkStarted) {
165 			eventWriter.writeEnd();
166 		}
167 		
168 		writeCategories(eventWriter, theBundle.getCategories());
169 
170 		writeOptionalTagWithTextNode(eventWriter, "totalResults", theBundle.getTotalResults());
171 
172 		writeAuthor(theBundle, eventWriter);
173 
174 		eventWriter.writeStartArray("entry");
175 		for (BundleEntry nextEntry : theBundle.getEntries()) {
176 			eventWriter.writeStartObject();
177 
178 			boolean deleted = nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false;
179 			if (deleted) {
180 				writeTagWithTextNode(eventWriter, "deleted", nextEntry.getDeletedAt());
181 			}
182 			writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
183 			writeTagWithTextNode(eventWriter, "id", nextEntry.getId());
184 
185 			linkStarted = false;
186 			linkStarted = writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf(), linkStarted);
187 			linkStarted = writeAtomLink(eventWriter, "alternate", nextEntry.getLinkAlternate(), linkStarted);
188 			linkStarted = writeAtomLink(eventWriter, "search", nextEntry.getLinkSearch(), linkStarted);
189 			if (linkStarted) {
190 				eventWriter.writeEnd();
191 			}
192 
193 			writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
194 			writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());
195 
196 			writeCategories(eventWriter, nextEntry.getCategories());
197 
198 			writeAuthor(nextEntry, eventWriter);
199 
200 			IResource resource = nextEntry.getResource();
201 			if (resource != null && !resource.isEmpty() && !deleted) {
202 				RuntimeResourceDefinition resDef = myContext.getResourceDefinition(resource);
203 				encodeResourceToJsonStreamWriter(resDef, resource, eventWriter, "content", false);
204 			}
205 
206 			if (nextEntry.getSummary().isEmpty()==false) {
207 				eventWriter.write("summary", nextEntry.getSummary().getValueAsString());
208 			}
209 			
210 			eventWriter.writeEnd(); // entry object
211 		}
212 		eventWriter.writeEnd(); // entry array
213 
214 		eventWriter.writeEnd();
215 		eventWriter.flush();
216 	}
217 
218 	private void writeCategories(JsonGenerator eventWriter, TagList categories) {
219 		if (categories != null && categories.size() > 0) {
220 			eventWriter.writeStartArray("category");
221 			for (Tag next : categories) {
222 				eventWriter.writeStartObject();
223 				eventWriter.write("term", defaultString(next.getTerm()));
224 				eventWriter.write("label", defaultString(next.getLabel()));
225 				eventWriter.write("scheme", defaultString(next.getScheme()));
226 				eventWriter.writeEnd();
227 			}
228 			eventWriter.writeEnd();
229 		}
230 	}
231 
232 	private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef,
233 			String theChildName, boolean theIsSubElementWithinResource) throws IOException {
234 
235 		switch (theChildDef.getChildType()) {
236 		case PRIMITIVE_DATATYPE: {
237 			IPrimitiveDatatype<?> value = (IPrimitiveDatatype<?>) theValue;
238 			if (isBlank(value.getValueAsString())) {
239 				break;
240 			}
241 			
242 			if (value instanceof IntegerDt) {
243 				if (theChildName != null) {
244 					theWriter.write(theChildName, ((IntegerDt) value).getValue());
245 				} else {
246 					theWriter.write(((IntegerDt) value).getValue());
247 				}
248 			} else if (value instanceof DecimalDt) {
249 				if (theChildName != null) {
250 					theWriter.write(theChildName, ((DecimalDt) value).getValue());
251 				} else {
252 					theWriter.write(((DecimalDt) value).getValue());
253 				}
254 			} else if (value instanceof BooleanDt) {
255 				if (theChildName != null) {
256 					theWriter.write(theChildName, ((BooleanDt) value).getValue());
257 				} else {
258 					theWriter.write(((BooleanDt) value).getValue());
259 				}
260 			} else {
261 				String valueStr = value.getValueAsString();
262 				if (theChildName != null) {
263 					theWriter.write(theChildName, valueStr);
264 				} else {
265 					theWriter.write(valueStr);
266 				}
267 			}
268 			break;
269 		}
270 		case RESOURCE_BLOCK:
271 		case COMPOSITE_DATATYPE: {
272 			BaseRuntimeElementCompositeDefinition<?> childCompositeDef = (BaseRuntimeElementCompositeDefinition<?>) theChildDef;
273 			if (theChildName != null) {
274 				theWriter.writeStartObject(theChildName);
275 			} else {
276 				theWriter.writeStartObject();
277 			}
278 			if (theValue instanceof ExtensionDt) {
279 				theWriter.write("url", ((ExtensionDt) theValue).getUrlAsString());
280 			}
281 			encodeCompositeElementToStreamWriter(theResDef, theResource, theValue, theWriter, childCompositeDef, theIsSubElementWithinResource);
282 			theWriter.writeEnd();
283 			break;
284 		}
285 		case RESOURCE_REF: {
286 			ResourceReferenceDt referenceDt = (ResourceReferenceDt) theValue;
287 			if (theChildName != null) {
288 				theWriter.writeStartObject(theChildName);
289 			} else {
290 				theWriter.writeStartObject();
291 			}
292 
293 			String reference = determineReferenceText(referenceDt); 
294 					
295 			if (StringUtils.isNotBlank(reference)) {
296 				theWriter.write(XmlParser.RESREF_REFERENCE, reference);
297 			}
298 			if (referenceDt.getDisplay().isEmpty() == false) {
299 				theWriter.write(XmlParser.RESREF_DISPLAY, referenceDt.getDisplay().getValueAsString());
300 			}
301 			theWriter.writeEnd();
302 			break;
303 		}
304 		case CONTAINED_RESOURCES: {
305 			theWriter.writeStartArray(theChildName);
306 			ContainedDt value = (ContainedDt) theValue;
307 			for (IResource next : value.getContainedResources()) {
308 				if (getContainedResources().getResourceId(next)!=null) {
309 					continue;
310 				}
311 				encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true, fixContainedResourceId(next.getId().getValue()));
312 			}
313 			for (IResource next : getContainedResources().getContainedResources()) {
314 				IdDt resourceId = getContainedResources().getResourceId(next);
315 				encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true, fixContainedResourceId(resourceId.getValue()));
316 			}
317 			theWriter.writeEnd();
318 			break;
319 		}
320 		case PRIMITIVE_XHTML: {
321 			if (!getSuppressNarratives()) {
322 				XhtmlDt dt = (XhtmlDt) theValue;
323 				if (theChildName != null) {
324 					theWriter.write(theChildName, dt.getValueAsString());
325 				} else {
326 					theWriter.write(dt.getValueAsString());
327 				}
328 			} else {
329 				if (theChildName != null) {
330 					// do nothing
331 				} else {
332 					theWriter.writeNull();
333 				}
334 			}
335 			break;
336 		}
337 		case UNDECL_EXT:
338 		default:
339 			throw new IllegalStateException("Should not have this state here: " + theChildDef.getChildType().name());
340 		}
341 
342 	}
343 
344 	private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter,
345 			List<? extends BaseRuntimeChildDefinition> theChildren, boolean theIsSubElementWithinResource) throws IOException {
346 		for (BaseRuntimeChildDefinition nextChild : theChildren) {
347 			if (nextChild instanceof RuntimeChildNarrativeDefinition) {
348 				INarrativeGenerator gen = myContext.getNarrativeGenerator();
349 				if (gen != null) {
350 					NarrativeDt narr = gen.generateNarrative(theResDef.getResourceProfile(), theResource);
351 					if (narr != null) {
352 						RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
353 						String childName = nextChild.getChildNameByDatatype(child.getDatatype());
354 						BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
355 						encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, type, childName, theIsSubElementWithinResource);
356 						continue;
357 					}
358 				}
359 			}
360 
361 			List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
362 			if (values == null || values.isEmpty()) {
363 				continue;
364 			}
365 
366 			String currentChildName = null;
367 			boolean inArray = false;
368 
369 			ArrayList<ArrayList<HeldExtension>> extensions = new ArrayList<ArrayList<HeldExtension>>(0);
370 			ArrayList<ArrayList<HeldExtension>> modifierExtensions = new ArrayList<ArrayList<HeldExtension>>(0);
371 
372 			int valueIdx = 0;
373 			for (IElement nextValue : values) {
374 				if (nextValue == null || nextValue.isEmpty()) {
375 					if (nextValue instanceof ContainedDt) {
376 						if (theIsSubElementWithinResource || getContainedResources().isEmpty()) {
377 							continue;
378 						}
379 					} else {
380 						continue;
381 					}
382 				}
383 
384 				Class<? extends IElement> type = nextValue.getClass();
385 				String childName = nextChild.getChildNameByDatatype(type);
386 				BaseRuntimeElementDefinition<?> childDef = nextChild.getChildElementDefinitionByDatatype(type);
387 				if (childDef == null) {
388 					super.throwExceptionForUnknownChildType(nextChild, type);
389 				}
390 				boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
391 				
392 				if (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES && theIsSubElementWithinResource) {
393 					continue;
394 				}
395 				
396 				if (nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
397 					// Don't encode extensions
398 //					RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild;
399 //					if (extDef.isModifier()) {
400 //						addToHeldExtensions(valueIdx, modifierExtensions, extDef, nextValue);
401 //					} else {
402 //						addToHeldExtensions(valueIdx, extensions, extDef, nextValue);
403 //					}
404 				} else {
405 
406 					if (currentChildName == null || !currentChildName.equals(childName)) {
407 						if (inArray) {
408 							theEventWriter.writeEnd();
409 						}
410 						if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
411 							theEventWriter.writeStartArray(childName);
412 							inArray = true;
413 							encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theIsSubElementWithinResource);
414 						} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theIsSubElementWithinResource) {
415 							// suppress narratives from contained resources
416 						} else {
417 							encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theIsSubElementWithinResource);
418 						}
419 						currentChildName = childName;
420 					} else {
421 						encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theIsSubElementWithinResource);
422 					}
423 
424 					if (nextValue instanceof ISupportsUndeclaredExtensions && primitive) {
425 						List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredExtensions();
426 						addToHeldExtensions(valueIdx, ext, extensions);
427 
428 						ext = ((ISupportsUndeclaredExtensions) nextValue).getUndeclaredModifierExtensions();
429 						addToHeldExtensions(valueIdx, ext, modifierExtensions);
430 					}
431 
432 				}
433 
434 				valueIdx++;
435 			}
436 
437 			if (inArray) {
438 				theEventWriter.writeEnd();
439 			}
440 
441 			if (extensions.size() > 0 || modifierExtensions.size() > 0) {
442 				theEventWriter.writeStartArray('_' + currentChildName);
443 
444 				for (int i = 0; i < valueIdx; i++) {
445 					boolean haveContent = false;
446 					if (extensions.size() > i && extensions.get(i) != null && extensions.get(i).isEmpty() == false) {
447 						haveContent = true;
448 						theEventWriter.writeStartObject();
449 						theEventWriter.writeStartArray("extension");
450 						for (HeldExtension nextExt : extensions.get(i)) {
451 							nextExt.write(theResDef, theResource, theEventWriter);
452 						}
453 						theEventWriter.writeEnd();
454 						theEventWriter.writeEnd();
455 					}
456 
457 					if (!haveContent) {
458 						// theEventWriter.writeEnd();
459 						theEventWriter.writeNull();
460 					}
461 				}
462 
463 				theEventWriter.writeEnd();
464 			}
465 		}
466 	}
467 
468 	private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter,
469 			BaseRuntimeElementCompositeDefinition<?> resDef, boolean theIsSubElementWithinResource) throws IOException, DataFormatException {
470 		extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, resDef, theResDef, theResource);
471 		encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIsSubElementWithinResource);
472 		encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren(),theIsSubElementWithinResource);
473 	}
474 
475 	private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull,
476 			boolean theIsSubElementWithinResource) throws IOException {
477 		String resourceId = null;
478 		if (theIsSubElementWithinResource && StringUtils.isNotBlank(theResource.getId().getValue())) {
479 			resourceId = theResource.getId().getValue();
480 		}
481 
482 		encodeResourceToJsonStreamWriter(theResDef, theResource, theEventWriter, theObjectNameOrNull, theIsSubElementWithinResource, resourceId);
483 	}
484 
485 	private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, boolean theIsSubElementWithinResource,
486 			String theResourceId) throws IOException {
487 		if (!theIsSubElementWithinResource) {
488 			super.containResourcesForEncoding(theResource);
489 		}
490 
491 		RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
492 
493 		if (theObjectNameOrNull == null) {
494 			theEventWriter.writeStartObject();
495 		} else {
496 			theEventWriter.writeStartObject(theObjectNameOrNull);
497 		}
498 
499 		theEventWriter.write("resourceType", resDef.getName());
500 		if (theResourceId != null) {
501 			theEventWriter.write("id", theResourceId);
502 		}
503 
504 		if (theResource instanceof Binary) {
505 			Binary bin = (Binary) theResource;
506 			theEventWriter.write("contentType", bin.getContentType());
507 			theEventWriter.write("content", bin.getContentAsBase64());
508 		} else {
509 			encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, resDef, theIsSubElementWithinResource);
510 		}
511 		theEventWriter.writeEnd();
512 	}
513 
514 	@Override
515 	public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException {
516 		Validate.notNull(theResource, "Resource can not be null");
517 
518 		JsonGenerator eventWriter = createJsonGenerator(theWriter);
519 
520 		RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
521 		encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null, false);
522 		eventWriter.flush();
523 	}
524 
525 	@Override
526 	public void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException {
527 		JsonGenerator eventWriter = createJsonGenerator(theWriter);
528 
529 		eventWriter.writeStartObject();
530 
531 		eventWriter.write("resourceType", TagList.ELEMENT_NAME);
532 
533 		eventWriter.writeStartArray(TagList.ATTR_CATEGORY);
534 		for (Tag next : theTagList) {
535 			eventWriter.writeStartObject();
536 
537 			if (isNotBlank(next.getTerm())) {
538 				eventWriter.write(Tag.ATTR_TERM, next.getTerm());
539 			}
540 			if (isNotBlank(next.getLabel())) {
541 				eventWriter.write(Tag.ATTR_LABEL, next.getLabel());
542 			}
543 			if (isNotBlank(next.getScheme())) {
544 				eventWriter.write(Tag.ATTR_SCHEME, next.getScheme());
545 			}
546 
547 			eventWriter.writeEnd();
548 		}
549 		eventWriter.writeEnd();
550 
551 		eventWriter.writeEnd();
552 		eventWriter.flush();
553 	}
554 
555 	/**
556 	 * This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object called _name): resource extensions, and extension extensions
557 	 */
558 	private void extractAndWriteExtensionsAsDirectChild(IElement theElement, JsonGenerator theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef,
559 			IResource theResource) throws IOException {
560 		List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
561 		List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
562 
563 		// Undeclared extensions
564 		extractUndeclaredExtensions(theElement, extensions, modifierExtensions);
565 
566 		// Declared extensions
567 		extractDeclaredExtensions(theElement, theElementDef, extensions, modifierExtensions);
568 
569 		// Write the extensions
570 		writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
571 	}
572 
573 	private void extractDeclaredExtensions(IElement theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions) {
574 		for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) {
575 			for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) {
576 				if (nextValue != null) {
577 					if (nextValue == null || nextValue.isEmpty()) {
578 						continue;
579 					}
580 					extensions.add(new HeldExtension(nextDef, nextValue));
581 				}
582 			}
583 		}
584 		for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) {
585 			for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) {
586 				if (nextValue != null) {
587 					if (nextValue == null || nextValue.isEmpty()) {
588 						continue;
589 					}
590 					modifierExtensions.add(new HeldExtension(nextDef, nextValue));
591 				}
592 			}
593 		}
594 	}
595 
596 	private void extractUndeclaredExtensions(IElement theResource, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions) {
597 		if (theResource instanceof ISupportsUndeclaredExtensions) {
598 			List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) theResource).getUndeclaredExtensions();
599 			for (ExtensionDt next : ext) {
600 				if (next == null || next.isEmpty()) {
601 					continue;
602 				}
603 				extensions.add(new HeldExtension(next));
604 			}
605 
606 			ext = ((ISupportsUndeclaredExtensions) theResource).getUndeclaredModifierExtensions();
607 			for (ExtensionDt next : ext) {
608 				if (next == null || next.isEmpty()) {
609 					continue;
610 				}
611 				modifierExtensions.add(new HeldExtension(next));
612 			}
613 		}
614 	}
615 
616 	private void parseAlternates(JsonValue theAlternateVal, ParserState<?> theState) {
617 		if (theAlternateVal == null || theAlternateVal.getValueType() == ValueType.NULL) {
618 			return;
619 		}
620 		JsonObject alternate = (JsonObject) theAlternateVal;
621 		for (Entry<String, JsonValue> nextEntry : alternate.entrySet()) {
622 			String nextKey = nextEntry.getKey();
623 			JsonValue nextVal = nextEntry.getValue();
624 			if ("extension".equals(nextKey)) {
625 				boolean isModifier = false;
626 				JsonArray array = (JsonArray) nextEntry.getValue();
627 				parseExtension(theState, array, isModifier);
628 			} else if ("modifierExtension".equals(nextKey)) {
629 				boolean isModifier = true;
630 				JsonArray array = (JsonArray) nextEntry.getValue();
631 				parseExtension(theState, array, isModifier);
632 			} else if ("id".equals(nextKey)) {
633 				switch (nextVal.getValueType()) {
634 				case STRING:
635 					theState.attributeValue("id", ((JsonString) nextVal).getString());
636 					break;
637 				case NULL:
638 					break;
639 				default:
640 					break;
641 				}
642 			}
643 		}
644 	}
645 
646 	@Override
647 	public <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
648 		JsonReader reader;		
649 		JsonObject object;
650 		
651 		try {
652 			reader = Json.createReader(theReader);
653 			object = reader.readObject();
654 		} catch (JsonParsingException e) {
655 			if (e.getMessage().startsWith("Unexpected char 39")) {
656 				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);
657 			}
658 			throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e);
659 		}
660 		JsonValue resourceTypeObj = object.get("resourceType");
661 		assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
662 		String resourceType = ((JsonString) resourceTypeObj).getString();
663 		if (!"Bundle".equals(resourceType)) {
664 			throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'");
665 		}
666 
667 		ParserState<Bundle> state = ParserState.getPreAtomInstance(myContext, theResourceType, true);
668 		state.enteringNewElement(null, "feed");
669 
670 		parseBundleChildren(object, state);
671 
672 		state.endingElement();
673 
674 		Bundle retVal = state.getObject();
675 
676 		return retVal;
677 	}
678 
679 	private void parseBundleChildren(JsonObject theObject, ParserState<?> theState) {
680 		for (String nextName : theObject.keySet()) {
681 			if ("resourceType".equals(nextName)) {
682 				continue;
683 			} else if ("link".equals(nextName)) {
684 				JsonArray entries = theObject.getJsonArray(nextName);
685 				for (JsonValue jsonValue : entries) {
686 					theState.enteringNewElement(null, "link");
687 					JsonObject linkObj = (JsonObject) jsonValue;
688 					String rel = linkObj.getString("rel", null);
689 					String href = linkObj.getString("href", null);
690 					theState.attributeValue("rel", rel);
691 					theState.attributeValue("href", href);
692 					theState.endingElement();
693 				}
694 				continue;
695 			} else if ("entry".equals(nextName)) {
696 				JsonArray entries = theObject.getJsonArray(nextName);
697 				for (JsonValue jsonValue : entries) {
698 					theState.enteringNewElement(null, "entry");
699 					parseBundleChildren((JsonObject) jsonValue, theState);
700 					theState.endingElement();
701 				}
702 				continue;
703 			} else if (BUNDLE_TEXTNODE_CHILDREN.contains(nextName)) {
704 				theState.enteringNewElement(null, nextName);
705 				theState.string(theObject.getString(nextName, null));
706 				theState.endingElement();
707 				continue;
708 			}
709 
710 			JsonValue nextVal = theObject.get(nextName);
711 			parseChildren(theState, nextName, nextVal, null);
712 
713 		}
714 	}
715 
716 	private void parseChildren(JsonObject theObject, ParserState<?> theState) {
717 		String elementId = null;
718 		for (String nextName : theObject.keySet()) {
719 			if ("resourceType".equals(nextName)) {
720 				continue;
721 			} else if ("id".equals(nextName)) {
722 				elementId = theObject.getString(nextName);
723 				continue;
724 			} else if ("_id".equals(nextName)) {
725 				// _id is incorrect, but some early examples in the FHIR spec used it
726 				elementId = theObject.getString(nextName);
727 				continue;
728 			} else if ("extension".equals(nextName)) {
729 				JsonArray array = theObject.getJsonArray(nextName);
730 				parseExtension(theState, array, false);
731 				continue;
732 			} else if ("modifierExtension".equals(nextName)) {
733 				JsonArray array = theObject.getJsonArray(nextName);
734 				parseExtension(theState, array, true);
735 				continue;
736 			} else if (nextName.charAt(0) == '_') {
737 				continue;
738 			}
739 
740 			JsonValue nextVal = theObject.get(nextName);
741 			JsonValue alternateVal = theObject.get('_' + nextName);
742 
743 			parseChildren(theState, nextName, nextVal, alternateVal);
744 
745 		}
746 
747 		if (elementId != null) {
748 			IElement object = (IElement) theState.getObject();
749 			if (object instanceof IIdentifiableElement) {
750 				((IIdentifiableElement) object).setElementSpecificId(elementId);
751 			} else if (object instanceof IResource) {
752 				((IResource) object).setId(new IdDt(elementId));
753 			}
754 		}
755 	}
756 
757 	private void parseChildren(ParserState<?> theState, String theName, JsonValue theJsonVal, JsonValue theAlternateVal) {
758 		switch (theJsonVal.getValueType()) {
759 		case ARRAY: {
760 			JsonArray nextArray = (JsonArray) theJsonVal;
761 			JsonArray nextAlternateArray = (JsonArray) theAlternateVal;
762 			for (int i = 0; i < nextArray.size(); i++) {
763 				JsonValue nextObject = nextArray.get(i);
764 				JsonValue nextAlternate = null;
765 				if (nextAlternateArray != null) {
766 					nextAlternate = nextAlternateArray.get(i);
767 				}
768 				parseChildren(theState, theName, nextObject, nextAlternate);
769 			}
770 			break;
771 		}
772 		case OBJECT: {
773 			theState.enteringNewElement(null, theName);
774 			parseAlternates(theAlternateVal, theState);
775 			JsonObject nextObject = (JsonObject) theJsonVal;
776 			boolean preResource = false;
777 			if (theState.isPreResource()) {
778 				String resType = nextObject.getString("resourceType");
779 				if (isBlank(resType)) {
780 					throw new DataFormatException("Missing 'resourceType' from resource");
781 				}
782 				theState.enteringNewElement(null, resType);
783 				preResource = true;
784 			}
785 			parseChildren(nextObject, theState);
786 			if (preResource) {
787 				theState.endingElement();
788 			}
789 			theState.endingElement();
790 			break;
791 		}
792 		case STRING: {
793 			JsonString nextValStr = (JsonString) theJsonVal;
794 			theState.enteringNewElement(null, theName);
795 			theState.attributeValue("value", nextValStr.getString());
796 			parseAlternates(theAlternateVal, theState);
797 			theState.endingElement();
798 			break;
799 		}
800 		case NUMBER:
801 		case FALSE:
802 		case TRUE:
803 			theState.enteringNewElement(null, theName);
804 			theState.attributeValue("value", theJsonVal.toString());
805 			parseAlternates(theAlternateVal, theState);
806 			theState.endingElement();
807 			break;
808 		case NULL:
809 			break;
810 		}
811 	}
812 
813 	private void parseExtension(ParserState<?> theState, JsonArray array, boolean theIsModifier) {
814 		for (int i = 0; i < array.size(); i++) {
815 			JsonObject nextExtObj = array.getJsonObject(i);
816 			String url = nextExtObj.getString("url");
817 			theState.enteringNewElementExtension(null, url, theIsModifier);
818 			for (Iterator<String> iter = nextExtObj.keySet().iterator(); iter.hasNext();) {
819 				String next = iter.next();
820 				if ("url".equals(next)) {
821 					continue;
822 				} else if ("extension".equals(next)) {
823 					JsonArray jsonVal = (JsonArray) nextExtObj.get(next);
824 					parseExtension(theState, jsonVal, false);
825 				} else if ("modifierExtension".equals(next)) {
826 					JsonArray jsonVal = (JsonArray) nextExtObj.get(next);
827 					parseExtension(theState, jsonVal, true);
828 				} else {
829 					JsonValue jsonVal = nextExtObj.get(next);
830 					parseChildren(theState, next, jsonVal, null);
831 				}
832 			}
833 			theState.endingElement();
834 		}
835 	}
836 
837 	@Override
838 	public <T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) {
839 		JsonReader reader = Json.createReader(theReader);
840 		JsonObject object = reader.readObject();
841 
842 		JsonValue resourceTypeObj = object.get("resourceType");
843 		assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
844 		String resourceType = ((JsonString) resourceTypeObj).getString();
845 
846 		RuntimeResourceDefinition def;
847 		if (theResourceType != null) {
848 			def = myContext.getResourceDefinition(theResourceType);
849 		} else {
850 			def = myContext.getResourceDefinition(resourceType);
851 		}
852 
853 		ParserState<? extends IResource> state = ParserState.getPreResourceInstance(def.getImplementingClass(), myContext, true);
854 		state.enteringNewElement(null, def.getName());
855 
856 		parseChildren(object, state);
857 
858 		state.endingElement();
859 
860 		@SuppressWarnings("unchecked")
861 		T retVal = (T) state.getObject();
862 
863 		return retVal;
864 	}
865 
866 	@Override
867 	public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) {
868 		return parseResource(theResourceType, new StringReader(theMessageString));
869 	}
870 
871 	@Override
872 	public TagList parseTagList(Reader theReader) {
873 		JsonReader reader = Json.createReader(theReader);
874 		JsonObject object = reader.readObject();
875 
876 		JsonValue resourceTypeObj = object.get("resourceType");
877 		assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
878 		String resourceType = ((JsonString) resourceTypeObj).getString();
879 
880 		ParserState<TagList> state = ParserState.getPreTagListInstance(myContext, true);
881 		state.enteringNewElement(null, resourceType);
882 
883 		parseChildren(object, state);
884 
885 		state.endingElement();
886 
887 		return state.getObject();
888 	}
889 
890 	@Override
891 	public IParser setPrettyPrint(boolean thePrettyPrint) {
892 		myPrettyPrint = thePrettyPrint;
893 		return this;
894 	}
895 
896 	private boolean writeAtomLink(JsonGenerator theEventWriter, String theRel, StringDt theLink, boolean theStarted) {
897 		boolean retVal = theStarted;
898 		if (isNotBlank(theLink.getValue())) {
899 			if (theStarted == false) {
900 				theEventWriter.writeStartArray("link");
901 				retVal = true;
902 			}
903 
904 			theEventWriter.writeStartObject();
905 			theEventWriter.write("rel", theRel);
906 			theEventWriter.write("href", theLink.getValue());
907 			theEventWriter.writeEnd();
908 		}
909 		return retVal;
910 	}
911 
912 	private void writeAuthor(BaseBundle theBundle, JsonGenerator eventWriter) {
913 		if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) {
914 			eventWriter.writeStartArray("author");
915 			eventWriter.writeStartObject();
916 			writeTagWithTextNode(eventWriter, "name", theBundle.getAuthorName());
917 			writeOptionalTagWithTextNode(eventWriter, "uri", theBundle.getAuthorUri());
918 			eventWriter.writeEnd();
919 			eventWriter.writeEnd();
920 		}
921 	}
922 
923 	private void writeExtensionsAsDirectChild(IResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
924 			List<HeldExtension> modifierExtensions) throws IOException {
925 		if (extensions.isEmpty() == false) {
926 			theEventWriter.writeStartArray("extension");
927 			for (HeldExtension next : extensions) {
928 				next.write(resDef, theResource, theEventWriter);
929 			}
930 			theEventWriter.writeEnd();
931 		}
932 		if (modifierExtensions.isEmpty() == false) {
933 			theEventWriter.writeStartArray("modifierExtension");
934 			for (HeldExtension next : modifierExtensions) {
935 				next.write(resDef, theResource, theEventWriter);
936 			}
937 			theEventWriter.writeEnd();
938 		}
939 	}
940 
941 	private void writeOptionalTagWithTextNode(JsonGenerator theEventWriter, String theElementName, IPrimitiveDatatype<?> theInstantDt) {
942 		String str = theInstantDt.getValueAsString();
943 		if (StringUtils.isNotBlank(str)) {
944 			theEventWriter.write(theElementName, theInstantDt.getValueAsString());
945 		}
946 	}
947 
948 	private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, IPrimitiveDatatype<?> theIdDt) {
949 		if (theIdDt != null && !theIdDt.isEmpty()) {
950 			theEventWriter.write(theElementName, theIdDt.getValueAsString());
951 		} else {
952 			theEventWriter.writeNull(theElementName);
953 		}
954 	}
955 
956 	private void writeTagWithTextNode(JsonGenerator theEventWriter, String theElementName, StringDt theStringDt) {
957 		if (StringUtils.isNotBlank(theStringDt.getValue())) {
958 			theEventWriter.write(theElementName, theStringDt.getValue());
959 		}
960 		// else {
961 		// theEventWriter.writeNull(theElementName);
962 		// }
963 	}
964 
965 	private class HeldExtension {
966 
967 		private RuntimeChildDeclaredExtensionDefinition myDef;
968 		private ExtensionDt myUndeclaredExtension;
969 		private IElement myValue;
970 
971 		public HeldExtension(ExtensionDt theUndeclaredExtension) {
972 			assert theUndeclaredExtension != null;
973 			myUndeclaredExtension = theUndeclaredExtension;
974 		}
975 
976 		public HeldExtension(RuntimeChildDeclaredExtensionDefinition theDef, IElement theValue) {
977 			assert theDef != null;
978 			assert theValue != null;
979 			myDef = theDef;
980 			myValue = theValue;
981 		}
982 
983 		public void write(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter) throws IOException {
984 			if (myUndeclaredExtension != null) {
985 				writeUndeclaredExt(theResDef, theResource, theEventWriter, myUndeclaredExtension);
986 			} else {
987 				theEventWriter.writeStartObject();
988 				theEventWriter.write("url", myDef.getExtensionUrl());
989 
990 				BaseRuntimeElementDefinition<?> def = myDef.getChildElementDefinitionByDatatype(myValue.getClass());
991 				if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
992 					extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource);
993 				} else {
994 					// encodeChildElementToStreamWriter(theResDef, theResource,
995 					// theEventWriter, myValue, def, "value" +
996 					// WordUtils.capitalize(def.getName()));
997 					String childName = myDef.getChildNameByDatatype(myValue.getClass());
998 					encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName,false);
999 				}
1000 
1001 				// theEventWriter.name(myUndeclaredExtension.get);
1002 
1003 				theEventWriter.writeEnd();
1004 			}
1005 		}
1006 
1007 		private void writeUndeclaredExt(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theEventWriter, ExtensionDt ext) throws IOException {
1008 			IElement value = ext.getValue();
1009 
1010 			theEventWriter.writeStartObject();
1011 			theEventWriter.write("url", ext.getUrl().getValue());
1012 
1013 			boolean noValue = value == null || value.isEmpty();
1014 			if (noValue && ext.getAllUndeclaredExtensions().isEmpty()) {
1015 				ourLog.debug("Extension with URL[{}] has no value", ext.getUrl().getValue());
1016 			} else if (noValue) {
1017 				theEventWriter.writeStartArray("extension");
1018 				for (ExtensionDt next : ext.getUndeclaredExtensions()) {
1019 					writeUndeclaredExt(theResDef, theResource, theEventWriter, next);
1020 				}
1021 				theEventWriter.writeEnd();
1022 			} else {
1023 				RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
1024 				String childName = extDef.getChildNameByDatatype(value.getClass());
1025 				if (childName == null) {
1026 					throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName());
1027 				}
1028 				BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
1029 				encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName,true);
1030 			}
1031 
1032 			// theEventWriter.name(myUndeclaredExtension.get);
1033 
1034 			theEventWriter.writeEnd();
1035 		}
1036 
1037 	}
1038 }