001package ca.uhn.fhir.parser;
002
003/*-
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import static org.apache.commons.lang3.StringUtils.isBlank;
024import static org.apache.commons.lang3.StringUtils.isNotBlank;
025
026import java.io.Reader;
027import java.io.Writer;
028import java.util.*;
029
030import javax.xml.namespace.QName;
031import javax.xml.stream.*;
032import javax.xml.stream.events.*;
033
034import org.apache.commons.lang3.StringUtils;
035import org.hl7.fhir.instance.model.api.*;
036
037import ca.uhn.fhir.context.*;
038import ca.uhn.fhir.model.api.*;
039import ca.uhn.fhir.model.base.composite.BaseCodingDt;
040import ca.uhn.fhir.model.primitive.*;
041import ca.uhn.fhir.narrative.INarrativeGenerator;
042import ca.uhn.fhir.rest.api.EncodingEnum;
043import ca.uhn.fhir.util.*;
044
045/**
046 * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use
047 * {@link FhirContext#newXmlParser()} to get an instance.
048 */
049public class XmlParser extends BaseParser /* implements IParser */ {
050
051        static final String ATOM_NS = "http://www.w3.org/2005/Atom";
052        static final String FHIR_NS = "http://hl7.org/fhir";
053        static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
054        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
055        static final String RESREF_DISPLAY = "display";
056        static final String RESREF_REFERENCE = "reference";
057        static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0";
058        static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
059
060        // private static final Set<String> RESOURCE_NAMESPACES;
061
062        private FhirContext myContext;
063        private boolean myPrettyPrint;
064
065        /**
066         * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke
067         * {@link FhirContext#newXmlParser()}.
068         * 
069         * @param theParserErrorHandler
070         */
071        public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
072                super(theContext, theParserErrorHandler);
073                myContext = theContext;
074        }
075
076        private XMLEventReader createStreamReader(Reader theReader) {
077                try {
078                        return XmlUtil.createXmlReader(theReader);
079                } catch (FactoryConfigurationError e1) {
080                        throw new ConfigurationException("Failed to initialize STaX event factory", e1);
081                } catch (XMLStreamException e1) {
082                        throw new DataFormatException(e1);
083                }
084        }
085
086        private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException {
087                XMLStreamWriter eventWriter;
088                eventWriter = XmlUtil.createXmlStreamWriter(theWriter);
089                eventWriter = decorateStreamWriter(eventWriter);
090                return eventWriter;
091        }
092
093        private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
094                if (myPrettyPrint) {
095                        PrettyPrintWriterWrapper retVal = new PrettyPrintWriterWrapper(eventWriter);
096                        return retVal;
097                }
098                NonPrettyPrintWriterWrapper retVal = new NonPrettyPrintWriterWrapper(eventWriter);
099                return retVal;
100        }
101
102        @Override
103        public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException {
104                XMLStreamWriter eventWriter;
105                try {
106                        eventWriter = createXmlWriter(theWriter);
107
108                        encodeResourceToXmlStreamWriter(theResource, eventWriter, false, false);
109                        eventWriter.flush();
110                } catch (XMLStreamException e) {
111                        throw new ConfigurationException("Failed to initialize STaX event factory", e);
112                }
113        }
114
115        @Override
116        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
117                XMLEventReader streamReader = createStreamReader(theReader);
118                return parseResource(theResourceType, streamReader);
119        }
120
121        private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) {
122                ourLog.trace("Entering XML parsing loop with state: {}", parserState);
123
124                try {
125                        List<String> heldComments = new ArrayList<String>(1);
126
127                        while (streamReader.hasNext()) {
128                                XMLEvent nextEvent = streamReader.nextEvent();
129                                try {
130
131                                        switch (nextEvent.getEventType()) {
132                                        case XMLStreamConstants.START_ELEMENT: {
133                                                StartElement elem = nextEvent.asStartElement();
134
135                                                String namespaceURI = elem.getName().getNamespaceURI();
136
137                                                if ("extension".equals(elem.getName().getLocalPart())) {
138                                                        Attribute urlAttr = elem.getAttributeByName(new QName("url"));
139                                                        String url;
140                                                        if (urlAttr == null || isBlank(urlAttr.getValue())) {
141                                                                getErrorHandler().missingRequiredElement(new ParseLocation("extension"), "url");
142                                                                url = null;
143                                                        } else {
144                                                                url = urlAttr.getValue();
145                                                        }
146                                                        parserState.enteringNewElementExtension(elem, url, false, getServerBaseUrl());
147                                                } else if ("modifierExtension".equals(elem.getName().getLocalPart())) {
148                                                        Attribute urlAttr = elem.getAttributeByName(new QName("url"));
149                                                        String url;
150                                                        if (urlAttr == null || isBlank(urlAttr.getValue())) {
151                                                                getErrorHandler().missingRequiredElement(new ParseLocation("modifierExtension"), "url");
152                                                                url = null;
153                                                        } else {
154                                                                url = urlAttr.getValue();
155                                                        }
156                                                        parserState.enteringNewElementExtension(elem, url, true, getServerBaseUrl());
157                                                } else {
158                                                        String elementName = elem.getName().getLocalPart();
159                                                        parserState.enteringNewElement(namespaceURI, elementName);
160                                                }
161
162                                                if (!heldComments.isEmpty()) {
163                                                        for (String next : heldComments) {
164                                                                parserState.commentPre(next);
165                                                        }
166                                                        heldComments.clear();
167                                                }
168
169                                                @SuppressWarnings("unchecked")
170                                                Iterator<Attribute> attributes = elem.getAttributes();
171                                                for (Iterator<Attribute> iter = attributes; iter.hasNext();) {
172                                                        Attribute next = iter.next();
173                                                        parserState.attributeValue(next.getName().getLocalPart(), next.getValue());
174                                                }
175
176                                                break;
177                                        }
178                                        case XMLStreamConstants.END_DOCUMENT:
179                                        case XMLStreamConstants.END_ELEMENT: {
180                                                if (!heldComments.isEmpty()) {
181                                                        for (String next : heldComments) {
182                                                                parserState.commentPost(next);
183                                                        }
184                                                        heldComments.clear();
185                                                }
186                                                parserState.endingElement();
187//                                              if (parserState.isComplete()) {
188//                                                      return parserState.getObject();
189//                                              }
190                                                break;
191                                        }
192                                        case XMLStreamConstants.CHARACTERS: {
193                                                parserState.string(nextEvent.asCharacters().getData());
194                                                break;
195                                        }
196                                        case XMLStreamConstants.COMMENT: {
197                                                Comment comment = (Comment) nextEvent;
198                                                String commentText = comment.getText();
199                                                heldComments.add(commentText);
200                                                break;
201                                        }
202                                        }
203
204                                        parserState.xmlEvent(nextEvent);
205
206                                } catch (DataFormatException e) {
207                                        throw new DataFormatException("DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e);
208                                }
209                        }
210                        return parserState.getObject();
211                } catch (XMLStreamException e) {
212                        throw new DataFormatException(e);
213                }
214        }
215
216        private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
217                        String theExtensionUrl, boolean theIncludedResource, boolean theSubResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException {
218                if (theElement == null || theElement.isEmpty()) {
219                        if (isChildContained(childDef, theIncludedResource)) {
220                                // We still want to go in..
221                        } else {
222                                return;
223                        }
224                }
225
226                writeCommentsPre(theEventWriter, theElement);
227
228                switch (childDef.getChildType()) {
229                case ID_DATATYPE: {
230      IIdType value = IIdType.class.cast(theElement);
231      String encodedValue = "id".equals(childName) ? value.getIdPart() : value.getValue();
232      if (StringUtils.isNotBlank(encodedValue) || super.hasExtensions(value)) {
233        theEventWriter.writeStartElement(childName);
234        if (StringUtils.isNotBlank(encodedValue)) {
235          theEventWriter.writeAttribute("value", encodedValue);
236        }
237        encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource, theSubResource);
238        theEventWriter.writeEndElement();
239      }
240      break;
241                }
242                case PRIMITIVE_DATATYPE: {
243                        IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
244                        String value = pd.getValueAsString();
245                        if (value != null || super.hasExtensions(pd)) {
246                                theEventWriter.writeStartElement(childName);
247                                String elementId = getCompositeElementId(theElement);
248                                if (isNotBlank(elementId)) {
249                                        theEventWriter.writeAttribute("id", elementId);
250                                }
251                                if (value != null) {
252                                        theEventWriter.writeAttribute("value", value);
253                                }
254                                encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource,theSubResource);
255                                theEventWriter.writeEndElement();
256                        }
257                        break;
258                }
259                case RESOURCE_BLOCK:
260                case COMPOSITE_DATATYPE: {
261                        theEventWriter.writeStartElement(childName);
262                        String elementId = getCompositeElementId(theElement);
263                        if (isNotBlank(elementId)) {
264                                theEventWriter.writeAttribute("id", elementId);
265                        }
266                        if (isNotBlank(theExtensionUrl)) {
267                                theEventWriter.writeAttribute("url", theExtensionUrl);
268                        }
269                        encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theSubResource, theParent);
270                        theEventWriter.writeEndElement();
271                        break;
272                }
273                case CONTAINED_RESOURCE_LIST:
274                case CONTAINED_RESOURCES: {
275                        /*
276                         * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
277                         * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
278                         * theEventWriter.writeEndElement(); }
279                         */
280                        for (IBaseResource next : getContainedResources().getContainedResources()) {
281                                IIdType resourceId = getContainedResources().getResourceId(next);
282                                theEventWriter.writeStartElement("contained");
283                                encodeResourceToXmlStreamWriter(next, theEventWriter, true, false, fixContainedResourceId(resourceId.getValue()));
284                                theEventWriter.writeEndElement();
285                        }
286                        break;
287                }
288                case RESOURCE: {
289                        theEventWriter.writeStartElement(childName);
290                        IBaseResource resource = (IBaseResource) theElement;
291                        encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true);
292                        theEventWriter.writeEndElement();
293                        break;
294                }
295                case PRIMITIVE_XHTML: {
296                        XhtmlDt dt = XhtmlDt.class.cast(theElement);
297                        if (dt.hasContent()) {
298                                encodeXhtml(dt, theEventWriter);
299                        }
300                        break;
301                }
302                case PRIMITIVE_XHTML_HL7ORG: {
303                        IBaseXhtml dt = IBaseXhtml.class.cast(theElement);
304                        if (!dt.isEmpty()) {
305                                // TODO: this is probably not as efficient as it could be
306                                XhtmlDt hdt = new XhtmlDt();
307                                hdt.setValueAsString(dt.getValueAsString());
308                                encodeXhtml(hdt, theEventWriter);
309                        }
310                        break;
311                }
312                case EXTENSION_DECLARED:
313                case UNDECL_EXT: {
314                        throw new IllegalStateException("state should not happen: " + childDef.getName());
315                }
316                }
317
318                writeCommentsPost(theEventWriter, theElement);
319
320        }
321
322        private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent)
323                        throws XMLStreamException, DataFormatException {
324
325                for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theSubResource, theParent)) {
326
327                        BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
328
329                        if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) {
330                                /*
331                                 * XML encoding is a one-off for extensions. The URL element goes in an attribute
332                                 * instead of being encoded as a normal element, only for XML encoding
333                                 */
334                                continue;
335                        }
336
337                        if (nextChild instanceof RuntimeChildNarrativeDefinition) {
338                                INarrativeGenerator gen = myContext.getNarrativeGenerator();
339                                INarrative narr;
340                                if (theResource instanceof IResource) {
341                                        narr = ((IResource) theResource).getText();
342                                } else if (theResource instanceof IDomainResource) {
343                                        narr = ((IDomainResource) theResource).getText();
344                                } else {
345                                        narr = null;
346                                }
347                                // FIXME potential null access on narr see line 623
348                                if (gen != null && narr.isEmpty()) {
349                                        gen.generateNarrative(myContext, theResource, narr);
350                                }
351                                if (narr != null && narr.isEmpty() == false) {
352                                        RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
353                                        String childName = nextChild.getChildNameByDatatype(child.getDatatype());
354                                        BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
355                                        encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, theSubResource, nextChildElem);
356                                        continue;
357                                }
358                        }
359
360                        if (nextChild instanceof RuntimeChildContainedResources) {
361                                encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, theSubResource, nextChildElem);
362                        } else {
363
364                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
365                                values = super.preProcessValues(nextChild, theResource, values, nextChildElem);
366
367                                if (values == null || values.isEmpty()) {
368                                        continue;
369                                }
370                                for (IBase nextValue : values) {
371                                        if ((nextValue == null || nextValue.isEmpty())) {
372                                                continue;
373                                        }
374
375                                        BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
376                                        if (childNameAndDef == null) {
377                                                continue;
378                                        }
379
380                                        String childName = childNameAndDef.getChildName();
381                                        BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
382                                        String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
383
384                                        if (extensionUrl != null && childName.equals("extension") == false) {
385                                                encodeExtension(theResource, theEventWriter, theContainedResource, theSubResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef);
386                                        } else if (nextChild instanceof RuntimeChildExtension) {
387                                                IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
388                                                if ((extension.getValue() == null || extension.getValue().isEmpty())) {
389                                                        if (extension.getExtension().isEmpty()) {
390                                                                continue;
391                                                        }
392                                                }
393                                                encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, theSubResource, nextChildElem);
394                                        } else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
395                                                // suppress narratives from contained resources
396                                        } else {
397                                                encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, theSubResource, nextChildElem);
398                                        }
399                                }
400                        }
401                }
402        }
403
404        private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef)
405                        throws XMLStreamException {
406                BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
407                if (extDef.isModifier()) {
408                        theEventWriter.writeStartElement("modifierExtension");
409                } else {
410                        theEventWriter.writeStartElement("extension");
411                }
412
413                String elementId = getCompositeElementId(nextValue);
414                if (isNotBlank(elementId)) {
415                        theEventWriter.writeAttribute("id", elementId);
416                }
417
418                theEventWriter.writeAttribute("url", extensionUrl);
419                encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, theSubResource, nextChildElem);
420                theEventWriter.writeEndElement();
421        }
422
423        private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException {
424                if (theElement instanceof ISupportsUndeclaredExtensions) {
425                        ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
426                        encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource, theSubResource);
427                        encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource,theSubResource);
428                }
429                if (theElement instanceof IBaseHasExtensions) {
430                        IBaseHasExtensions res = (IBaseHasExtensions) theElement;
431                        encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource,theSubResource);
432                }
433                if (theElement instanceof IBaseHasModifierExtensions) {
434                        IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
435                        encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource,theSubResource);
436                }
437        }
438
439        private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException {
440                IIdType resourceId = null;
441
442                if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
443                        resourceId = theResource.getIdElement();
444                        if (theResource.getIdElement().getValue().startsWith("urn:")) {
445                                resourceId = null;
446                        }
447                }
448
449                if (!theIncludedResource) {
450                        if (super.shouldEncodeResourceId(theResource, theSubResource) == false) {
451                                resourceId = null;
452                        } else if (theSubResource == false && getEncodeForceResourceId() != null) {
453                                resourceId = getEncodeForceResourceId();
454                        }
455                }
456
457                encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, theSubResource, resourceId);
458        }
459
460        private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, boolean theSubResource,  IIdType theResourceId) throws XMLStreamException {
461                if (!theContainedResource) {
462                        super.containResourcesForEncoding(theResource);
463                }
464
465                RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
466                if (resDef == null) {
467                        throw new ConfigurationException("Unknown resource type: " + theResource.getClass());
468                }
469
470                theEventWriter.writeStartElement(resDef.getName());
471                theEventWriter.writeDefaultNamespace(FHIR_NS);
472
473                if (theResource instanceof IAnyResource) {
474                        // HL7.org Structures
475     if (theResourceId != null) {
476        writeCommentsPre(theEventWriter, theResourceId);
477        theEventWriter.writeStartElement("id");
478        theEventWriter.writeAttribute("value", theResourceId.getIdPart());
479        encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, false);
480        theEventWriter.writeEndElement();
481        writeCommentsPost(theEventWriter, theResourceId);
482      }
483
484                        encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
485
486                } else {
487
488                        // DSTU2+
489
490                        IResource resource = (IResource) theResource;
491        if (theResourceId != null) {
492          /*    writeCommentsPre(theEventWriter, theResourceId);
493              writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
494                                            writeCommentsPost(theEventWriter, theResourceId);*/
495          theEventWriter.writeStartElement("id");
496          theEventWriter.writeAttribute("value", theResourceId.getIdPart());
497          encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false,false);
498          theEventWriter.writeEndElement();
499          writeCommentsPost(theEventWriter, theResourceId);
500        }
501
502                        InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
503                        IdDt resourceId = resource.getId();
504                        String versionIdPart = resourceId.getVersionIdPart();
505                        if (isBlank(versionIdPart)) {
506                                versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
507                        }
508                        List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
509                        List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
510                        profiles = super.getProfileTagsForEncoding(resource, profiles);
511
512                        TagList tags = getMetaTagsForEncoding((resource));
513
514                        if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
515                                theEventWriter.writeStartElement("meta");
516                                writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart);
517                                if (updated != null) {
518                                        writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
519                                }
520
521                                for (IIdType profile : profiles) {
522                                        theEventWriter.writeStartElement("profile");
523                                        theEventWriter.writeAttribute("value", profile.getValue());
524                                        theEventWriter.writeEndElement();
525                                }
526                                for (BaseCodingDt securityLabel : securityLabels) {
527                                        theEventWriter.writeStartElement("security");
528                                        encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, theSubResource, null);
529                                        theEventWriter.writeEndElement();
530                                }
531                                if (tags != null) {
532                                        for (Tag tag : tags) {
533                                                if (tag.isEmpty()) {
534                                                        continue;
535                                                }
536                                                theEventWriter.writeStartElement("tag");
537                                                writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme());
538                                                writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm());
539                                                writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel());
540                                                theEventWriter.writeEndElement();
541                                        }
542                                }
543                                theEventWriter.writeEndElement();
544                        }
545
546                        if (theResource instanceof IBaseBinary) {
547                                IBaseBinary bin = (IBaseBinary) theResource;
548                                writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
549                                writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
550                        } else {
551                                encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
552                        }
553
554                }
555
556                theEventWriter.writeEndElement();
557        }
558
559        private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource, boolean theSubResource)
560                        throws XMLStreamException, DataFormatException {
561                for (IBaseExtension<?, ?> next : theExtensions) {
562                        if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
563                                continue;
564                        }
565
566                        writeCommentsPre(theEventWriter, next);
567
568                        theEventWriter.writeStartElement(tagName);
569
570                        String elementId = getCompositeElementId(next);
571                        if (isNotBlank(elementId)) {
572                                theEventWriter.writeAttribute("id", elementId);
573                        }
574
575                        String url = getExtensionUrl(next.getUrl());
576                        theEventWriter.writeAttribute("url", url);
577
578                        if (next.getValue() != null) {
579                                IBaseDatatype value = next.getValue();
580                                RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
581                                String childName = extDef.getChildNameByDatatype(value.getClass());
582                                BaseRuntimeElementDefinition<?> childDef;
583                                if (childName == null) {
584                                        childDef = myContext.getElementDefinition(value.getClass());
585                                        if (childDef == null) {
586                                                throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
587                                        }
588                                        childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef);
589                                } else {
590                                        childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
591                                        if (childDef == null) {
592                                                throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
593                                        }
594                                }
595                                encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, theSubResource, null);
596                        }
597
598                        // child extensions
599                        encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource,theSubResource);
600
601                        theEventWriter.writeEndElement();
602
603                        writeCommentsPost(theEventWriter, next);
604
605                }
606        }
607
608        private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
609                if (theDt == null || theDt.getValue() == null) {
610                        return;
611                }
612
613                boolean firstElement = true;
614                for (XMLEvent event : theDt.getValue()) {
615                        switch (event.getEventType()) {
616                        case XMLStreamConstants.ATTRIBUTE:
617                                Attribute attr = (Attribute) event;
618                                if (isBlank(attr.getName().getPrefix())) {
619                                        if (isBlank(attr.getName().getNamespaceURI())) {
620                                                theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue());
621                                        } else {
622                                                theEventWriter.writeAttribute(attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
623                                        }
624                                } else {
625                                        theEventWriter.writeAttribute(attr.getName().getPrefix(), attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
626                                }
627
628                                break;
629                        case XMLStreamConstants.CDATA:
630                                theEventWriter.writeCData(((Characters) event).getData());
631                                break;
632                        case XMLStreamConstants.CHARACTERS:
633                        case XMLStreamConstants.SPACE:
634                                String data = ((Characters) event).getData();
635                                theEventWriter.writeCharacters(data);
636                                break;
637                        case XMLStreamConstants.COMMENT:
638                                theEventWriter.writeComment(((Comment) event).getText());
639                                break;
640                        case XMLStreamConstants.END_ELEMENT:
641                                theEventWriter.writeEndElement();
642                                break;
643                        case XMLStreamConstants.ENTITY_REFERENCE:
644                                EntityReference er = (EntityReference) event;
645                                theEventWriter.writeEntityRef(er.getName());
646                                break;
647                        case XMLStreamConstants.NAMESPACE:
648                                Namespace ns = (Namespace) event;
649                                theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI());
650                                break;
651                        case XMLStreamConstants.START_ELEMENT:
652                                StartElement se = event.asStartElement();
653                                if (firstElement) {
654                                        if (StringUtils.isBlank(se.getName().getPrefix())) {
655                                                String namespaceURI = se.getName().getNamespaceURI();
656                                                if (StringUtils.isBlank(namespaceURI)) {
657                                                        namespaceURI = "http://www.w3.org/1999/xhtml";
658                                                }
659                                                theEventWriter.writeStartElement(se.getName().getLocalPart());
660                                                theEventWriter.writeDefaultNamespace(namespaceURI);
661                                        } else {
662                                                String prefix = se.getName().getPrefix();
663                                                String namespaceURI = se.getName().getNamespaceURI();
664                                                theEventWriter.writeStartElement(prefix, se.getName().getLocalPart(), namespaceURI);
665                                                theEventWriter.writeNamespace(prefix, namespaceURI);
666                                        }
667                                        firstElement = false;
668                                } else {
669                                        if (isBlank(se.getName().getPrefix())) {
670                                                if (isBlank(se.getName().getNamespaceURI())) {
671                                                        theEventWriter.writeStartElement(se.getName().getLocalPart());
672                                                } else {
673                                                        if (StringUtils.isBlank(se.getName().getPrefix())) {
674                                                                theEventWriter.writeStartElement(se.getName().getLocalPart());
675                                                                // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
676                                                        } else {
677                                                                theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart());
678                                                        }
679                                                }
680                                        } else {
681                                                theEventWriter.writeStartElement(se.getName().getPrefix(), se.getName().getLocalPart(), se.getName().getNamespaceURI());
682                                        }
683                                        for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext();) {
684                                                Attribute next = (Attribute) attrIter.next();
685                                                theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue());
686                                        }
687                                }
688                                break;
689                        case XMLStreamConstants.DTD:
690                        case XMLStreamConstants.END_DOCUMENT:
691                        case XMLStreamConstants.ENTITY_DECLARATION:
692                        case XMLStreamConstants.NOTATION_DECLARATION:
693                        case XMLStreamConstants.PROCESSING_INSTRUCTION:
694                        case XMLStreamConstants.START_DOCUMENT:
695                                break;
696                        }
697
698                }
699        }
700
701        @Override
702        public EncodingEnum getEncoding() {
703                return EncodingEnum.XML;
704        }
705
706        private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
707                ParserState<T> parserState = ParserState.getPreResourceInstance(this, theResourceType, myContext, false, getErrorHandler());
708                return doXmlLoop(theStreamReader, parserState);
709        }
710
711        @Override
712        public IParser setPrettyPrint(boolean thePrettyPrint) {
713                myPrettyPrint = thePrettyPrint;
714                return this;
715        }
716
717        /**
718         * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to
719         * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be
720         * rejected by the compiler some of the time.
721         */
722        private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) {
723                List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size());
724                retVal.addAll(theList);
725                return retVal;
726        }
727
728        private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
729                if (theElement != null && theElement.hasFormatComment()) {
730                        for (String next : theElement.getFormatCommentsPost()) {
731                                if (isNotBlank(next)) {
732                                        theEventWriter.writeComment(next);
733                                }
734                        }
735                }
736        }
737
738        private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
739                if (theElement != null && theElement.hasFormatComment()) {
740                        for (String next : theElement.getFormatCommentsPre()) {
741                                if (isNotBlank(next)) {
742                                        theEventWriter.writeComment(next);
743                                }
744                        }
745                }
746        }
747
748        private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException {
749                if (StringUtils.isNotBlank(theValue)) {
750                        theEventWriter.writeStartElement(theName);
751                        theEventWriter.writeAttribute("value", theValue);
752                        theEventWriter.writeEndElement();
753                }
754        }
755
756}