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.defaultString;
24  import static org.apache.commons.lang3.StringUtils.isBlank;
25  import static org.apache.commons.lang3.StringUtils.isNotBlank;
26  
27  import java.io.IOException;
28  import java.io.Reader;
29  import java.io.StringWriter;
30  import java.io.Writer;
31  import java.util.Iterator;
32  import java.util.List;
33  
34  import javax.xml.namespace.QName;
35  import javax.xml.stream.FactoryConfigurationError;
36  import javax.xml.stream.XMLEventReader;
37  import javax.xml.stream.XMLStreamConstants;
38  import javax.xml.stream.XMLStreamException;
39  import javax.xml.stream.XMLStreamWriter;
40  import javax.xml.stream.events.Attribute;
41  import javax.xml.stream.events.Characters;
42  import javax.xml.stream.events.Comment;
43  import javax.xml.stream.events.EntityReference;
44  import javax.xml.stream.events.Namespace;
45  import javax.xml.stream.events.StartElement;
46  import javax.xml.stream.events.XMLEvent;
47  
48  import org.apache.commons.lang3.StringUtils;
49  
50  import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
51  import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
52  import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
53  import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
54  import ca.uhn.fhir.context.ConfigurationException;
55  import ca.uhn.fhir.context.FhirContext;
56  import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
57  import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
58  import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
59  import ca.uhn.fhir.context.RuntimeResourceDefinition;
60  import ca.uhn.fhir.model.api.Bundle;
61  import ca.uhn.fhir.model.api.BundleEntry;
62  import ca.uhn.fhir.model.api.ExtensionDt;
63  import ca.uhn.fhir.model.api.IElement;
64  import ca.uhn.fhir.model.api.IPrimitiveDatatype;
65  import ca.uhn.fhir.model.api.IResource;
66  import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
67  import ca.uhn.fhir.model.api.Tag;
68  import ca.uhn.fhir.model.api.TagList;
69  import ca.uhn.fhir.model.dstu.composite.ContainedDt;
70  import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
71  import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
72  import ca.uhn.fhir.model.dstu.resource.Binary;
73  import ca.uhn.fhir.model.primitive.IdDt;
74  import ca.uhn.fhir.model.primitive.InstantDt;
75  import ca.uhn.fhir.model.primitive.StringDt;
76  import ca.uhn.fhir.model.primitive.XhtmlDt;
77  import ca.uhn.fhir.narrative.INarrativeGenerator;
78  import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper;
79  import ca.uhn.fhir.util.PrettyPrintWriterWrapper;
80  import ca.uhn.fhir.util.XmlUtil;
81  
82  public class XmlParser extends BaseParser implements IParser {
83  	static final String RESREF_DISPLAY = "display";
84  	static final String RESREF_REFERENCE = "reference";
85  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
86  	static final String ATOM_NS = "http://www.w3.org/2005/Atom";
87  	static final String FHIR_NS = "http://hl7.org/fhir";
88  	static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
89  	static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0";
90  	static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
91  
92  	// private static final Set<String> RESOURCE_NAMESPACES;
93  
94  	private FhirContext myContext;
95  	private boolean myPrettyPrint;
96  
97  	public XmlParser(FhirContext theContext) {
98  		super(theContext);
99  		myContext = theContext;
100 	}
101 
102 	@Override
103 	public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
104 		StringWriter stringWriter = new StringWriter();
105 		encodeBundleToWriter(theBundle, stringWriter);
106 
107 		return stringWriter.toString();
108 	}
109 
110 	@Override
111 	public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException {
112 		try {
113 			XMLStreamWriter eventWriter = createXmlWriter(theWriter);
114 
115 			eventWriter.writeStartElement("feed");
116 			eventWriter.writeDefaultNamespace(ATOM_NS);
117 
118 			writeTagWithTextNode(eventWriter, "title", theBundle.getTitle());
119 			writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId());
120 
121 			writeAtomLink(eventWriter, "self", theBundle.getLinkSelf());
122 			writeAtomLink(eventWriter, "first", theBundle.getLinkFirst());
123 			writeAtomLink(eventWriter, "previous", theBundle.getLinkPrevious());
124 			writeAtomLink(eventWriter, "next", theBundle.getLinkNext());
125 			writeAtomLink(eventWriter, "last", theBundle.getLinkLast());
126 			writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase());
127 
128 			if (theBundle.getTotalResults().getValue() != null) {
129 				eventWriter.writeStartElement("os", "totalResults", OPENSEARCH_NS);
130 				eventWriter.writeNamespace("os", OPENSEARCH_NS);
131 				eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString());
132 				eventWriter.writeEndElement();
133 			}
134 
135 			writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated());
136 			writeOptionalTagWithTextNode(eventWriter, "published", theBundle.getPublished());
137 
138 			if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) {
139 				eventWriter.writeStartElement("author");
140 				writeTagWithTextNode(eventWriter, "name", theBundle.getAuthorName());
141 				writeOptionalTagWithTextNode(eventWriter, "uri", theBundle.getAuthorUri());
142 				eventWriter.writeEndElement();
143 			}
144 
145 			writeCategories(eventWriter, theBundle.getCategories());
146 
147 			for (BundleEntry nextEntry : theBundle.getEntries()) {
148 				boolean deleted = false;
149 				if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) {
150 					deleted = true;
151 					eventWriter.writeStartElement("at", "deleted-entry", TOMBSTONES_NS);
152 					eventWriter.writeNamespace("at", TOMBSTONES_NS);
153 					eventWriter.writeAttribute("ref", nextEntry.getId().getValueAsString());
154 					eventWriter.writeAttribute("when", nextEntry.getDeletedAt().getValueAsString());
155 					if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty() == false) {
156 						eventWriter.writeStartElement(TOMBSTONES_NS, "by");
157 						if (nextEntry.getDeletedByName().isEmpty() == false) {
158 							eventWriter.writeStartElement(TOMBSTONES_NS, "name");
159 							eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue());
160 							eventWriter.writeEndElement();
161 						}
162 						if (nextEntry.getDeletedByEmail().isEmpty() == false) {
163 							eventWriter.writeStartElement(TOMBSTONES_NS, "email");
164 							eventWriter.writeCharacters(nextEntry.getDeletedByEmail().getValue());
165 							eventWriter.writeEndElement();
166 						}
167 						eventWriter.writeEndElement();
168 					}
169 					if (nextEntry.getDeletedComment().isEmpty() == false) {
170 						eventWriter.writeStartElement(TOMBSTONES_NS, "comment");
171 						eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue());
172 						eventWriter.writeEndElement();
173 					}
174 				} else {
175 					eventWriter.writeStartElement("entry");
176 				}
177 
178 				writeOptionalTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
179 				if (!deleted) {
180 					writeTagWithTextNode(eventWriter, "id", nextEntry.getId());
181 				}
182 				writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
183 				writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());
184 
185 				writeCategories(eventWriter, nextEntry.getCategories());
186 
187 				if (!nextEntry.getLinkSelf().isEmpty()) {
188 					writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
189 				}
190 
191 				if (!nextEntry.getLinkAlternate().isEmpty()) {
192 					writeAtomLink(eventWriter, "alternate", nextEntry.getLinkAlternate());
193 				}
194 
195 				if (!nextEntry.getLinkSearch().isEmpty()) {
196 					writeAtomLink(eventWriter, "search", nextEntry.getLinkSearch());
197 				}
198 
199 				IResource resource = nextEntry.getResource();
200 				if (resource != null && !resource.isEmpty() && !deleted) {
201 					eventWriter.writeStartElement("content");
202 					eventWriter.writeAttribute("type", "text/xml");
203 					encodeResourceToXmlStreamWriter(resource, eventWriter, false);
204 					eventWriter.writeEndElement(); // content
205 				} else {
206 					ourLog.debug("Bundle entry contains null resource");
207 				}
208 
209 				if (!nextEntry.getSummary().isEmpty()) {
210 					eventWriter.writeStartElement("summary");
211 					eventWriter.writeAttribute("type", "xhtml");
212 					encodeXhtml(nextEntry.getSummary(), eventWriter);
213 					eventWriter.writeEndElement();
214 				}
215 
216 				eventWriter.writeEndElement(); // entry
217 			}
218 
219 			eventWriter.writeEndElement();
220 			eventWriter.close();
221 		} catch (XMLStreamException e) {
222 			throw new ConfigurationException("Failed to initialize STaX event factory", e);
223 		}
224 	}
225 
226 	private void writeCategories(XMLStreamWriter eventWriter, TagList categories) throws XMLStreamException {
227 		if (categories != null) {
228 			for (Tag next : categories) {
229 				eventWriter.writeStartElement("category");
230 				eventWriter.writeAttribute("term", defaultString(next.getTerm()));
231 				eventWriter.writeAttribute("label", defaultString(next.getLabel()));
232 				eventWriter.writeAttribute("scheme", defaultString(next.getScheme()));
233 				eventWriter.writeEndElement();
234 			}
235 		}
236 	}
237 
238 	@Override
239 	public String encodeResourceToString(IResource theResource) throws DataFormatException {
240 		if (theResource == null) {
241 			throw new NullPointerException("Resource can not be null");
242 		}
243 
244 		Writer stringWriter = new StringWriter();
245 		encodeResourceToWriter(theResource, stringWriter);
246 		return stringWriter.toString();
247 	}
248 
249 	@Override
250 	public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws DataFormatException {
251 		XMLStreamWriter eventWriter;
252 		try {
253 			eventWriter = createXmlWriter(theWriter);
254 
255 			encodeResourceToXmlStreamWriter(theResource, eventWriter, false);
256 			eventWriter.flush();
257 		} catch (XMLStreamException e) {
258 			throw new ConfigurationException("Failed to initialize STaX event factory", e);
259 		}
260 	}
261 
262 	private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException {
263 		XMLStreamWriter eventWriter;
264 		eventWriter = XmlUtil.createXmlStreamWriter(theWriter);
265 		eventWriter = decorateStreamWriter(eventWriter);
266 		return eventWriter;
267 	}
268 
269 	@Override
270 	public void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException {
271 		try {
272 			XMLStreamWriter eventWriter = createXmlWriter(theWriter);
273 
274 			eventWriter.writeStartElement(TagList.ELEMENT_NAME_LC);
275 			eventWriter.writeDefaultNamespace(FHIR_NS);
276 
277 			for (Tag next : theTagList) {
278 				eventWriter.writeStartElement(TagList.ATTR_CATEGORY);
279 
280 				if (isNotBlank(next.getTerm())) {
281 					eventWriter.writeAttribute(Tag.ATTR_TERM, next.getTerm());
282 				}
283 				if (isNotBlank(next.getLabel())) {
284 					eventWriter.writeAttribute(Tag.ATTR_LABEL, next.getLabel());
285 				}
286 				if (isNotBlank(next.getScheme())) {
287 					eventWriter.writeAttribute(Tag.ATTR_SCHEME, next.getScheme());
288 				}
289 
290 				eventWriter.writeEndElement();
291 			}
292 
293 			eventWriter.writeEndElement();
294 			eventWriter.close();
295 		} catch (XMLStreamException e) {
296 			throw new ConfigurationException("Failed to initialize STaX event factory", e);
297 		}
298 	}
299 
300 	@Override
301 	public <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
302 		XMLEventReader streamReader = createStreamReader(theReader);
303 
304 		return parseBundle(streamReader, theResourceType);
305 	}
306 
307 	@Override
308 	public <T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) {
309 		XMLEventReader streamReader = createStreamReader(theReader);
310 
311 		return parseResource(theResourceType, streamReader);
312 	}
313 
314 	@Override
315 	public TagList parseTagList(Reader theReader) {
316 		XMLEventReader streamReader = createStreamReader(theReader);
317 
318 		ParserState<TagList> parserState = ParserState.getPreTagListInstance(myContext, false);
319 		return doXmlLoop(streamReader, parserState);
320 	}
321 
322 	@Override
323 	public IParser setPrettyPrint(boolean thePrettyPrint) {
324 		myPrettyPrint = thePrettyPrint;
325 		return this;
326 	}
327 
328 	private XMLEventReader createStreamReader(Reader theReader) {
329 		try {
330 			return XmlUtil.createXmlReader(theReader);
331 		} catch (FactoryConfigurationError e1) {
332 			throw new ConfigurationException("Failed to initialize STaX event factory", e1);
333 		} catch (XMLStreamException e1) {
334 			throw new DataFormatException(e1);
335 		}
336 
337 		// XMLEventReader streamReader;
338 		// try {
339 		// streamReader = myXmlInputFactory.createXMLEventReader(theReader);
340 		// } catch (XMLStreamException e) {
341 		// throw new DataFormatException(e);
342 		// } catch (FactoryConfigurationError e) {
343 		// throw new ConfigurationException("Failed to initialize STaX event factory", e);
344 		// }
345 		// return streamReader;
346 	}
347 
348 	private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
349 		if (myPrettyPrint) {
350 			PrettyPrintWriterWrapper retVal = new PrettyPrintWriterWrapper(eventWriter);
351 			return retVal;
352 		} else {
353 			NonPrettyPrintWriterWrapper retVal = new NonPrettyPrintWriterWrapper(eventWriter);
354 			return retVal;
355 		}
356 	}
357 
358 	private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) {
359 		ourLog.trace("Entering XML parsing loop with state: {}", parserState);
360 
361 		try {
362 			while (streamReader.hasNext()) {
363 				XMLEvent nextEvent = streamReader.nextEvent();
364 				try {
365 					if (nextEvent.isStartElement()) {
366 						StartElement elem = nextEvent.asStartElement();
367 
368 						String namespaceURI = elem.getName().getNamespaceURI();
369 
370 						if ("extension".equals(elem.getName().getLocalPart())) {
371 							Attribute urlAttr = elem.getAttributeByName(new QName("url"));
372 							if (urlAttr == null || isBlank(urlAttr.getValue())) {
373 								throw new DataFormatException("Extension element has no 'url' attribute");
374 							}
375 							parserState.enteringNewElementExtension(elem, urlAttr.getValue(), false);
376 						} else if ("modifierExtension".equals(elem.getName().getLocalPart())) {
377 							Attribute urlAttr = elem.getAttributeByName(new QName("url"));
378 							if (urlAttr == null || isBlank(urlAttr.getValue())) {
379 								throw new DataFormatException("Extension element has no 'url' attribute");
380 							}
381 							parserState.enteringNewElementExtension(elem, urlAttr.getValue(), true);
382 
383 						} else {
384 
385 							String elementName = elem.getName().getLocalPart();
386 							parserState.enteringNewElement(namespaceURI, elementName);
387 
388 						}
389 
390 						for (@SuppressWarnings("unchecked")
391 						Iterator<Attribute> iter = elem.getAttributes(); iter.hasNext();) {
392 							Attribute next = iter.next();
393 							// if
394 							// (next.getName().getLocalPart().equals("value")) {
395 							parserState.attributeValue(next.getName().getLocalPart(), next.getValue());
396 							// }
397 						}
398 
399 					} else if (nextEvent.isAttribute()) {
400 						Attribute elem = (Attribute) nextEvent;
401 						String name = (elem.getName().getLocalPart());
402 						parserState.attributeValue(name, elem.getValue());
403 					} else if (nextEvent.isEndElement()) {
404 						parserState.endingElement();
405 						if (parserState.isComplete()) {
406 							return parserState.getObject();
407 						}
408 					} else if (nextEvent.isCharacters()) {
409 						parserState.string(nextEvent.asCharacters().getData());
410 					}
411 
412 					parserState.xmlEvent(nextEvent);
413 
414 				} catch (DataFormatException e) {
415 					throw new DataFormatException("DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e);
416 				}
417 			}
418 			return null;
419 		} catch (XMLStreamException e) {
420 			throw new DataFormatException(e);
421 		}
422 	}
423 
424 	private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theEventWriter, IElement nextValue, String childName,
425 			BaseRuntimeElementDefinition<?> childDef, String theExtensionUrl, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
426 		if (nextValue.isEmpty()) {
427 			if (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES && getContainedResources().isEmpty()==false && theIncludedResource == false) {
428 				// We still want to go in..
429 			} else {
430 				return;
431 			}
432 		}
433 
434 		switch (childDef.getChildType()) {
435 		case PRIMITIVE_DATATYPE: {
436 			IPrimitiveDatatype<?> pd = (IPrimitiveDatatype<?>) nextValue;
437 			String value = pd.getValueAsString();
438 			if (value != null) {
439 				theEventWriter.writeStartElement(childName);
440 				theEventWriter.writeAttribute("value", value);
441 				encodeExtensionsIfPresent(theResDef, theResource, theEventWriter, nextValue, theIncludedResource);
442 				theEventWriter.writeEndElement();
443 			}
444 			break;
445 		}
446 		case RESOURCE_BLOCK:
447 		case COMPOSITE_DATATYPE: {
448 			theEventWriter.writeStartElement(childName);
449 			if (isNotBlank(theExtensionUrl)) {
450 				theEventWriter.writeAttribute("url", theExtensionUrl);
451 			}
452 			BaseRuntimeElementCompositeDefinition<?> childCompositeDef = (BaseRuntimeElementCompositeDefinition<?>) childDef;
453 			encodeCompositeElementToStreamWriter(theResDef, theResource, nextValue, theEventWriter, childCompositeDef, theIncludedResource);
454 			theEventWriter.writeEndElement();
455 			break;
456 		}
457 		case RESOURCE_REF: {
458 			ResourceReferenceDt ref = (ResourceReferenceDt) nextValue;
459 			if (!ref.isEmpty()) {
460 				theEventWriter.writeStartElement(childName);
461 				encodeResourceReferenceToStreamWriter(theEventWriter, ref);
462 				theEventWriter.writeEndElement();
463 			}
464 			break;
465 		}
466 		case CONTAINED_RESOURCES: {
467 			ContainedDt value = (ContainedDt) nextValue;
468 			theEventWriter.writeStartElement("contained");
469 			for (IResource next : value.getContainedResources()) {
470 				if (getContainedResources().getResourceId(next)!=null) {
471 					continue;
472 				}
473 				encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
474 			}
475 			for (IResource next : getContainedResources().getContainedResources()) {
476 				IdDt resourceId = getContainedResources().getResourceId(next);
477 				encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()));
478 			}
479 			theEventWriter.writeEndElement();
480 			break;
481 		}
482 		case RESOURCE: {
483 			throw new IllegalStateException(); // should not happen
484 		}
485 		case PRIMITIVE_XHTML: {
486 			XhtmlDt dt = (XhtmlDt) nextValue;
487 			if (dt.hasContent()) {
488 				encodeXhtml(dt, theEventWriter);
489 			}
490 			break;
491 		}
492 		case EXTENSION_DECLARED:
493 		case UNDECL_EXT: {
494 			throw new IllegalStateException("state should not happen: " + childDef.getName());
495 		}
496 		}
497 
498 	}
499 
500 
501 	private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter,
502 			List<? extends BaseRuntimeChildDefinition> children, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
503 		for (BaseRuntimeChildDefinition nextChild : children) {
504 			if (nextChild instanceof RuntimeChildNarrativeDefinition && !theIncludedResource) {
505 				INarrativeGenerator gen = myContext.getNarrativeGenerator();
506 				NarrativeDt narr = theResource.getText();
507 				if (gen != null && narr.isEmpty()) {
508 					narr = gen.generateNarrative(theResDef.getResourceProfile(), theResource);
509 				}
510 				if (narr != null) {
511 					RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
512 					String childName = nextChild.getChildNameByDatatype(child.getDatatype());
513 					BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
514 					encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, narr, childName, type, null, theIncludedResource);
515 					continue;
516 				}
517 			}
518 
519 			List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
520 			if (values == null || values.isEmpty()) {
521 				continue;
522 			}
523 
524 			for (IElement nextValue : values) {
525 				if ((nextValue == null || nextValue.isEmpty()) && !(nextValue instanceof ContainedDt)) {
526 					continue;
527 				}
528 				Class<? extends IElement> type = nextValue.getClass();
529 				String childName = nextChild.getChildNameByDatatype(type);
530 				String extensionUrl = nextChild.getExtensionUrl();
531 				BaseRuntimeElementDefinition<?> childDef = nextChild.getChildElementDefinitionByDatatype(type);
532 				if (childDef == null) {
533 					super.throwExceptionForUnknownChildType(nextChild, type);
534 				}
535 
536 				if (nextValue instanceof ExtensionDt) {
537 
538 					extensionUrl = ((ExtensionDt) nextValue).getUrlAsString();
539 					encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theIncludedResource);
540 
541 				} else if (extensionUrl != null && childName.equals("extension") == false) {
542 					RuntimeChildDeclaredExtensionDefinition extDef = (RuntimeChildDeclaredExtensionDefinition) nextChild;
543 					if (extDef.isModifier()) {
544 						theEventWriter.writeStartElement("modifierExtension");
545 					} else {
546 						theEventWriter.writeStartElement("extension");
547 					}
548 
549 					theEventWriter.writeAttribute("url", extensionUrl);
550 					encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childName, childDef, null, theIncludedResource);
551 					theEventWriter.writeEndElement();
552 				} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theIncludedResource) {
553 					// suppress narratives from contained resources
554 				} else {
555 					encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theIncludedResource);
556 				}
557 			}
558 		}
559 	}
560 
561 	private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, XMLStreamWriter theEventWriter,
562 			BaseRuntimeElementCompositeDefinition<?> resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
563 		encodeExtensionsIfPresent(theResDef, theResource, theEventWriter, theElement, theIncludedResource);
564 		encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIncludedResource);
565 		encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren(), theIncludedResource);
566 	}
567 
568 	private void encodeExtensionsIfPresent(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, IElement theElement, boolean theIncludedResource)
569 			throws XMLStreamException, DataFormatException {
570 		if (theElement instanceof ISupportsUndeclaredExtensions) {
571 			ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
572 			encodeUndeclaredExtensions(theResDef, theResource, theWriter, res.getUndeclaredExtensions(), "extension", theIncludedResource);
573 			encodeUndeclaredExtensions(theResDef, theResource, theWriter, res.getUndeclaredModifierExtensions(), "modifierExtension", theIncludedResource);
574 		}
575 	}
576 
577 	private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, ResourceReferenceDt theRef) throws XMLStreamException {
578 		String reference = determineReferenceText(theRef);
579 		
580 		if (StringUtils.isNotBlank(reference)) {
581 			theEventWriter.writeStartElement(RESREF_REFERENCE);
582 			theEventWriter.writeAttribute("value", reference);
583 			theEventWriter.writeEndElement();
584 		}
585 		if (!(theRef.getDisplay().isEmpty())) {
586 			theEventWriter.writeStartElement(RESREF_DISPLAY);
587 			theEventWriter.writeAttribute("value", theRef.getDisplay().getValue());
588 			theEventWriter.writeEndElement();
589 		}
590 	}
591 
592 
593 	private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
594 		String resourceId = null;
595 		if (theIncludedResource && StringUtils.isNotBlank(theResource.getId().getValue())) {
596 			resourceId = theResource.getId().getValue();
597 		}
598 		
599 		encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId);
600 	}
601 
602 	private void encodeResourceToXmlStreamWriter(IResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, String theResourceId) throws XMLStreamException {
603 		if (!theIncludedResource) {
604 			super.containResourcesForEncoding(theResource);
605 		}
606 
607 		RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
608 		if (resDef == null) {
609 			throw new ConfigurationException("Unknown resource type: " + theResource.getClass());
610 		}
611 
612 		theEventWriter.writeStartElement(resDef.getName());
613 		theEventWriter.writeDefaultNamespace(FHIR_NS);
614 
615 		if (theResourceId != null) {
616 			theEventWriter.writeAttribute("id", theResourceId);
617 		}
618 
619 		if (theResource instanceof Binary) {
620 			Binary bin = (Binary) theResource;
621 			theEventWriter.writeAttribute("contentType", bin.getContentType());
622 			theEventWriter.writeCharacters(bin.getContentAsBase64());
623 		} else {
624 			encodeCompositeElementToStreamWriter(resDef, theResource, theResource, theEventWriter, resDef, theIncludedResource);
625 		}
626 
627 		theEventWriter.writeEndElement();
628 	}
629 
630 	private void encodeUndeclaredExtensions(RuntimeResourceDefinition theResDef, IResource theResource, XMLStreamWriter theWriter, List<ExtensionDt> extensions, String tagName,
631 			boolean theIncludedResource) throws XMLStreamException, DataFormatException {
632 		for (ExtensionDt next : extensions) {
633 			theWriter.writeStartElement(tagName);
634 			theWriter.writeAttribute("url", next.getUrl().getValue());
635 
636 			if (next.getValue() != null) {
637 				IElement nextValue = next.getValue();
638 				RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
639 				String childName = extDef.getChildNameByDatatype(nextValue.getClass());
640 				if (childName == null) {
641 					throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + nextValue.getClass().getCanonicalName());
642 				}
643 				BaseRuntimeElementDefinition<?> childDef = extDef.getChildElementDefinitionByDatatype(nextValue.getClass());
644 				encodeChildElementToStreamWriter(theResDef, theResource, theWriter, nextValue, childName, childDef, null, theIncludedResource);
645 			}
646 
647 			// child extensions
648 			encodeExtensionsIfPresent(theResDef, theResource, theWriter, next, theIncludedResource);
649 
650 			theWriter.writeEndElement();
651 		}
652 	}
653 
654 	private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
655 		if (theDt == null || theDt.getValue() == null || getSuppressNarratives()) {
656 			return;
657 		}
658 
659 		boolean firstElement = true;
660 		for (XMLEvent event : theDt.getValue()) {
661 			switch (event.getEventType()) {
662 			case XMLStreamConstants.ATTRIBUTE:
663 				Attribute attr = (Attribute) event;
664 				if (isBlank(attr.getName().getPrefix())) {
665 					if (isBlank(attr.getName().getNamespaceURI())) {
666 						theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue());
667 					} else {
668 						theEventWriter.writeAttribute(attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
669 					}
670 				} else {
671 					theEventWriter.writeAttribute(attr.getName().getPrefix(), attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
672 				}
673 
674 				break;
675 			case XMLStreamConstants.CDATA:
676 				theEventWriter.writeCData(((Characters) event).getData());
677 				break;
678 			case XMLStreamConstants.CHARACTERS:
679 			case XMLStreamConstants.SPACE:
680 				String data = ((Characters) event).getData();
681 				theEventWriter.writeCharacters(data);
682 				break;
683 			case XMLStreamConstants.COMMENT:
684 				theEventWriter.writeComment(((Comment) event).getText());
685 				break;
686 			case XMLStreamConstants.END_ELEMENT:
687 				theEventWriter.writeEndElement();
688 				break;
689 			case XMLStreamConstants.ENTITY_REFERENCE:
690 				EntityReference er = (EntityReference) event;
691 				theEventWriter.writeEntityRef(er.getName());
692 				break;
693 			case XMLStreamConstants.NAMESPACE:
694 				Namespace ns = (Namespace) event;
695 				theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI());
696 				break;
697 			case XMLStreamConstants.START_ELEMENT:
698 				StartElement se = event.asStartElement();
699 				if (firstElement) {
700 					if (StringUtils.isBlank(se.getName().getPrefix())) {
701 						String namespaceURI = se.getName().getNamespaceURI();
702 						if (StringUtils.isBlank(namespaceURI)) {
703 							namespaceURI = "http://www.w3.org/1999/xhtml";
704 						}
705 						theEventWriter.writeStartElement(se.getName().getLocalPart());
706 						theEventWriter.writeDefaultNamespace(namespaceURI);
707 					} else {
708 						String prefix = se.getName().getPrefix();
709 						String namespaceURI = se.getName().getNamespaceURI();
710 						theEventWriter.writeStartElement(prefix, se.getName().getLocalPart(), namespaceURI);
711 						theEventWriter.writeNamespace(prefix, namespaceURI);
712 					}
713 					firstElement = false;
714 				} else {
715 					if (isBlank(se.getName().getPrefix())) {
716 						if (isBlank(se.getName().getNamespaceURI())) {
717 							theEventWriter.writeStartElement(se.getName().getLocalPart());
718 						} else {
719 							if (StringUtils.isBlank(se.getName().getPrefix())) {
720 								theEventWriter.writeStartElement(se.getName().getLocalPart());
721 								// theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
722 							} else {
723 								theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart());
724 							}
725 						}
726 					} else {
727 						theEventWriter.writeStartElement(se.getName().getPrefix(), se.getName().getLocalPart(), se.getName().getNamespaceURI());
728 					}
729 					for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext();) {
730 						Attribute next = (Attribute) attrIter.next();
731 						theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue());
732 					}
733 				}
734 				break;
735 			case XMLStreamConstants.DTD:
736 			case XMLStreamConstants.END_DOCUMENT:
737 			case XMLStreamConstants.ENTITY_DECLARATION:
738 			case XMLStreamConstants.NOTATION_DECLARATION:
739 			case XMLStreamConstants.PROCESSING_INSTRUCTION:
740 			case XMLStreamConstants.START_DOCUMENT:
741 				break;
742 			}
743 
744 		}
745 	}
746 
747 	private Bundle parseBundle(XMLEventReader theStreamReader, Class<? extends IResource> theResourceType) {
748 		ParserState<Bundle> parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false);
749 		return doXmlLoop(theStreamReader, parserState);
750 	}
751 
752 	private <T extends IResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
753 		ParserState<T> parserState = ParserState.getPreResourceInstance(theResourceType, myContext, false);
754 		return doXmlLoop(theStreamReader, parserState);
755 	}
756 
757 	private void writeAtomLink(XMLStreamWriter theEventWriter, String theRel, StringDt theStringDt) throws XMLStreamException {
758 		if (StringUtils.isNotBlank(theStringDt.getValue())) {
759 			theEventWriter.writeStartElement("link");
760 			theEventWriter.writeAttribute("rel", theRel);
761 			theEventWriter.writeAttribute("href", theStringDt.getValue());
762 			theEventWriter.writeEndElement();
763 		}
764 	}
765 
766 	private void writeOptionalTagWithTextNode(XMLStreamWriter theEventWriter, String theTagName, InstantDt theInstantDt) throws XMLStreamException {
767 		if (theInstantDt.getValue() != null) {
768 			theEventWriter.writeStartElement(theTagName);
769 			theEventWriter.writeCharacters(theInstantDt.getValueAsString());
770 			theEventWriter.writeEndElement();
771 		}
772 	}
773 
774 	private void writeOptionalTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theTextValue) throws XMLStreamException {
775 		if (StringUtils.isNotBlank(theTextValue.getValue())) {
776 			theEventWriter.writeStartElement(theElementName);
777 			theEventWriter.writeCharacters(theTextValue.getValue());
778 			theEventWriter.writeEndElement();
779 		}
780 	}
781 
782 	private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, IdDt theIdDt) throws XMLStreamException {
783 		theEventWriter.writeStartElement(theElementName);
784 		if (StringUtils.isNotBlank(theIdDt.getValue())) {
785 			theEventWriter.writeCharacters(theIdDt.getValue());
786 		}
787 		theEventWriter.writeEndElement();
788 	}
789 
790 	private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theStringDt) throws XMLStreamException {
791 		theEventWriter.writeStartElement(theElementName);
792 		if (StringUtils.isNotBlank(theStringDt.getValue())) {
793 			theEventWriter.writeCharacters(theStringDt.getValue());
794 		}
795 		theEventWriter.writeEndElement();
796 	}
797 }