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