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