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