001/*-
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.parser;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
025import ca.uhn.fhir.context.ConfigurationException;
026import ca.uhn.fhir.context.FhirContext;
027import ca.uhn.fhir.context.RuntimeChildContainedResources;
028import ca.uhn.fhir.context.RuntimeChildExtension;
029import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
030import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
031import ca.uhn.fhir.context.RuntimeResourceDefinition;
032import ca.uhn.fhir.i18n.Msg;
033import ca.uhn.fhir.model.api.IResource;
034import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
035import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
036import ca.uhn.fhir.model.api.Tag;
037import ca.uhn.fhir.model.api.TagList;
038import ca.uhn.fhir.model.base.composite.BaseCodingDt;
039import ca.uhn.fhir.model.primitive.IdDt;
040import ca.uhn.fhir.model.primitive.InstantDt;
041import ca.uhn.fhir.model.primitive.XhtmlDt;
042import ca.uhn.fhir.narrative.INarrativeGenerator;
043import ca.uhn.fhir.rest.api.EncodingEnum;
044import ca.uhn.fhir.util.ElementUtil;
045import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper;
046import ca.uhn.fhir.util.PrettyPrintWriterWrapper;
047import ca.uhn.fhir.util.XmlUtil;
048import org.apache.commons.lang3.StringUtils;
049import org.hl7.fhir.instance.model.api.IAnyResource;
050import org.hl7.fhir.instance.model.api.IBase;
051import org.hl7.fhir.instance.model.api.IBaseBinary;
052import org.hl7.fhir.instance.model.api.IBaseDatatype;
053import org.hl7.fhir.instance.model.api.IBaseExtension;
054import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
055import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
056import org.hl7.fhir.instance.model.api.IBaseResource;
057import org.hl7.fhir.instance.model.api.IBaseXhtml;
058import org.hl7.fhir.instance.model.api.IIdType;
059import org.hl7.fhir.instance.model.api.IPrimitiveType;
060
061import java.io.IOException;
062import java.io.Reader;
063import java.io.Writer;
064import java.util.ArrayList;
065import java.util.Iterator;
066import java.util.List;
067import java.util.Optional;
068import javax.xml.namespace.QName;
069import javax.xml.stream.FactoryConfigurationError;
070import javax.xml.stream.XMLEventReader;
071import javax.xml.stream.XMLStreamConstants;
072import javax.xml.stream.XMLStreamException;
073import javax.xml.stream.XMLStreamWriter;
074import javax.xml.stream.events.Attribute;
075import javax.xml.stream.events.Characters;
076import javax.xml.stream.events.Comment;
077import javax.xml.stream.events.EntityReference;
078import javax.xml.stream.events.Namespace;
079import javax.xml.stream.events.StartElement;
080import javax.xml.stream.events.XMLEvent;
081
082import static org.apache.commons.lang3.StringUtils.isBlank;
083import static org.apache.commons.lang3.StringUtils.isNotBlank;
084
085/**
086 * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use
087 * {@link FhirContext#newXmlParser()} to get an instance.
088 */
089public class XmlParser extends BaseParser {
090
091        static final String FHIR_NS = "http://hl7.org/fhir";
092        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
093        private boolean myPrettyPrint;
094
095        /**
096         * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke
097         * {@link FhirContext#newXmlParser()}.
098         *
099         * @param theParserErrorHandler
100         */
101        public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
102                super(theContext, theParserErrorHandler);
103        }
104
105        private XMLEventReader createStreamReader(Reader theReader) {
106                try {
107                        return XmlUtil.createXmlReader(theReader);
108                } catch (FactoryConfigurationError e1) {
109                        throw new ConfigurationException(Msg.code(1848) + "Failed to initialize STaX event factory", e1);
110                } catch (XMLStreamException e1) {
111                        throw new DataFormatException(Msg.code(1849) + e1);
112                }
113        }
114
115        private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException {
116                XMLStreamWriter eventWriter;
117                eventWriter = XmlUtil.createXmlStreamWriter(theWriter);
118                eventWriter = decorateStreamWriter(eventWriter);
119                return eventWriter;
120        }
121
122        private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
123                if (myPrettyPrint) {
124                        PrettyPrintWriterWrapper retVal = new PrettyPrintWriterWrapper(eventWriter);
125                        return retVal;
126                }
127                NonPrettyPrintWriterWrapper retVal = new NonPrettyPrintWriterWrapper(eventWriter);
128                return retVal;
129        }
130
131        @Override
132        public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext)
133                        throws DataFormatException {
134                XMLStreamWriter eventWriter;
135                try {
136                        eventWriter = createXmlWriter(theWriter);
137
138                        encodeResourceToXmlStreamWriter(theResource, eventWriter, false, theEncodeContext);
139                        eventWriter.flush();
140                } catch (XMLStreamException e) {
141                        throw new ConfigurationException(Msg.code(1850) + "Failed to initialize STaX event factory", e);
142                }
143        }
144
145        @Override
146        protected void doEncodeToWriter(IBase theElement, Writer theWriter, EncodeContext theEncodeContext)
147                        throws IOException, DataFormatException {
148                XMLStreamWriter eventWriter;
149                try {
150                        eventWriter = createXmlWriter(theWriter);
151
152                        eventWriter.writeStartElement("element");
153                        encodeCompositeElementToStreamWriter(
154                                        null,
155                                        theElement,
156                                        eventWriter,
157                                        false,
158                                        new CompositeChildElement(null, theEncodeContext),
159                                        theEncodeContext);
160                        eventWriter.writeEndElement();
161
162                        eventWriter.flush();
163                } catch (XMLStreamException e) {
164                        throw new ConfigurationException(Msg.code(2365) + "Failed to initialize STaX event factory", e);
165                }
166        }
167
168        @Override
169        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
170                XMLEventReader streamReader = createStreamReader(theReader);
171                return parseResource(theResourceType, streamReader);
172        }
173
174        private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) {
175                ourLog.trace("Entering XML parsing loop with state: {}", parserState);
176
177                try {
178                        List<String> heldComments = new ArrayList<>(1);
179
180                        while (streamReader.hasNext()) {
181                                XMLEvent nextEvent = streamReader.nextEvent();
182                                try {
183
184                                        switch (nextEvent.getEventType()) {
185                                                case XMLStreamConstants.START_ELEMENT: {
186                                                        StartElement elem = nextEvent.asStartElement();
187
188                                                        String namespaceURI = elem.getName().getNamespaceURI();
189
190                                                        String localPart = elem.getName().getLocalPart();
191                                                        if ("extension".equals(localPart)) {
192                                                                Attribute urlAttr = elem.getAttributeByName(new QName("url"));
193                                                                String url;
194                                                                if (urlAttr == null || isBlank(urlAttr.getValue())) {
195                                                                        getErrorHandler()
196                                                                                        .missingRequiredElement(
197                                                                                                        new ParseLocation().setParentElementName("extension"), "url");
198                                                                        url = null;
199                                                                } else {
200                                                                        url = urlAttr.getValue();
201                                                                }
202                                                                parserState.enteringNewElementExtension(elem, url, false, getServerBaseUrl());
203                                                        } else if ("modifierExtension".equals(localPart)) {
204                                                                Attribute urlAttr = elem.getAttributeByName(new QName("url"));
205                                                                String url;
206                                                                if (urlAttr == null || isBlank(urlAttr.getValue())) {
207                                                                        getErrorHandler()
208                                                                                        .missingRequiredElement(
209                                                                                                        new ParseLocation().setParentElementName("modifierExtension"),
210                                                                                                        "url");
211                                                                        url = null;
212                                                                } else {
213                                                                        url = urlAttr.getValue();
214                                                                }
215                                                                parserState.enteringNewElementExtension(elem, url, true, getServerBaseUrl());
216                                                        } else {
217                                                                parserState.enteringNewElement(namespaceURI, localPart);
218                                                        }
219
220                                                        if (!heldComments.isEmpty()) {
221                                                                for (String next : heldComments) {
222                                                                        parserState.commentPre(next);
223                                                                }
224                                                                heldComments.clear();
225                                                        }
226
227                                                        for (Iterator<Attribute> attributes = elem.getAttributes(); attributes.hasNext(); ) {
228                                                                Attribute next = attributes.next();
229                                                                parserState.attributeValue(next.getName().getLocalPart(), next.getValue());
230                                                        }
231
232                                                        break;
233                                                }
234                                                case XMLStreamConstants.END_DOCUMENT:
235                                                case XMLStreamConstants.END_ELEMENT: {
236                                                        if (!heldComments.isEmpty()) {
237                                                                for (String next : heldComments) {
238                                                                        parserState.commentPost(next);
239                                                                }
240                                                                heldComments.clear();
241                                                        }
242                                                        parserState.endingElement();
243                                                        break;
244                                                }
245                                                case XMLStreamConstants.CHARACTERS: {
246                                                        parserState.string(nextEvent.asCharacters().getData());
247                                                        break;
248                                                }
249                                                case XMLStreamConstants.COMMENT: {
250                                                        Comment comment = (Comment) nextEvent;
251                                                        String commentText = comment.getText();
252                                                        heldComments.add(commentText);
253                                                        break;
254                                                }
255                                        }
256
257                                        parserState.xmlEvent(nextEvent);
258
259                                } catch (DataFormatException e) {
260                                        throw new DataFormatException(
261                                                        Msg.code(1851) + "DataFormatException at ["
262                                                                        + nextEvent.getLocation().toString() + "]: " + e.getMessage(),
263                                                        e);
264                                }
265                        }
266                        return parserState.getObject();
267                } catch (XMLStreamException e) {
268                        throw new DataFormatException(Msg.code(1852) + e);
269                }
270        }
271
272        private void encodeChildElementToStreamWriter(
273                        IBaseResource theResource,
274                        XMLStreamWriter theEventWriter,
275                        BaseRuntimeChildDefinition theChildDefinition,
276                        IBase theElement,
277                        String theChildName,
278                        BaseRuntimeElementDefinition<?> childDef,
279                        String theExtensionUrl,
280                        boolean theIncludedResource,
281                        CompositeChildElement theParent,
282                        EncodeContext theEncodeContext)
283                        throws XMLStreamException, DataFormatException {
284
285                /*
286                 * Often the two values below will be the same thing. There are cases though
287                 * where they will not be. An example would be Observation.value, which is
288                 * a choice type. If the value contains a Quantity, then:
289                 * childGenericName = "value"
290                 * theChildName = "valueQuantity"
291                 */
292                String childGenericName = theChildDefinition.getElementName();
293
294                theEncodeContext.pushPath(childGenericName, false);
295                try {
296
297                        if (theElement == null || theElement.isEmpty()) {
298                                if (isChildContained(childDef, theIncludedResource)) {
299                                        // We still want to go in..
300                                } else {
301                                        return;
302                                }
303                        }
304
305                        writeCommentsPre(theEventWriter, theElement);
306
307                        switch (childDef.getChildType()) {
308                                case ID_DATATYPE: {
309                                        IIdType value = IIdType.class.cast(theElement);
310                                        String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
311                                        if (StringUtils.isNotBlank(encodedValue) || !super.hasNoExtensions(value)) {
312                                                theEventWriter.writeStartElement(theChildName);
313                                                if (StringUtils.isNotBlank(encodedValue)) {
314                                                        theEventWriter.writeAttribute("value", encodedValue);
315                                                }
316                                                encodeExtensionsIfPresent(
317                                                                theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
318                                                theEventWriter.writeEndElement();
319                                        }
320                                        break;
321                                }
322                                case PRIMITIVE_DATATYPE: {
323                                        IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
324                                        String value = pd.getValueAsString();
325                                        if (value != null || !super.hasNoExtensions(pd)) {
326                                                theEventWriter.writeStartElement(theChildName);
327                                                String elementId = getCompositeElementId(theElement);
328                                                if (isNotBlank(elementId)) {
329                                                        theEventWriter.writeAttribute("id", elementId);
330                                                }
331                                                if (value != null) {
332                                                        theEventWriter.writeAttribute("value", value);
333                                                }
334                                                encodeExtensionsIfPresent(
335                                                                theResource, theEventWriter, theElement, theIncludedResource, theEncodeContext);
336                                                theEventWriter.writeEndElement();
337                                        }
338                                        break;
339                                }
340                                case RESOURCE_BLOCK:
341                                case COMPOSITE_DATATYPE: {
342                                        theEventWriter.writeStartElement(theChildName);
343                                        String elementId = getCompositeElementId(theElement);
344                                        if (isNotBlank(elementId)) {
345                                                theEventWriter.writeAttribute("id", elementId);
346                                        }
347                                        if (isNotBlank(theExtensionUrl)) {
348                                                theEventWriter.writeAttribute("url", theExtensionUrl);
349                                        }
350                                        encodeCompositeElementToStreamWriter(
351                                                        theResource, theElement, theEventWriter, theIncludedResource, theParent, theEncodeContext);
352                                        theEventWriter.writeEndElement();
353                                        break;
354                                }
355                                case CONTAINED_RESOURCE_LIST:
356                                case CONTAINED_RESOURCES: {
357                                        /*
358                                         * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
359                                         * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
360                                         * theEventWriter.writeEndElement(); }
361                                         */
362                                        for (IBaseResource next : getContainedResources().getContainedResources()) {
363                                                IIdType resourceId = getContainedResources().getResourceId(next);
364                                                theEventWriter.writeStartElement("contained");
365                                                String value = resourceId.getValue();
366                                                encodeResourceToXmlStreamWriter(
367                                                                next, theEventWriter, true, fixContainedResourceId(value), theEncodeContext);
368                                                theEventWriter.writeEndElement();
369                                        }
370                                        break;
371                                }
372                                case RESOURCE: {
373                                        IBaseResource resource = (IBaseResource) theElement;
374                                        String resourceName = getContext().getResourceType(resource);
375                                        if (!super.shouldEncodeResource(resourceName, theEncodeContext)) {
376                                                break;
377                                        }
378                                        theEventWriter.writeStartElement(theChildName);
379                                        theEncodeContext.pushPath(resourceName, true);
380                                        encodeResourceToXmlStreamWriter(resource, theEventWriter, theIncludedResource, theEncodeContext);
381                                        theEncodeContext.popPath();
382                                        theEventWriter.writeEndElement();
383                                        break;
384                                }
385                                case PRIMITIVE_XHTML: {
386                                        XhtmlDt dt = XhtmlDt.class.cast(theElement);
387                                        if (dt.hasContent()) {
388                                                encodeXhtml(dt, theEventWriter);
389                                        }
390                                        break;
391                                }
392                                case PRIMITIVE_XHTML_HL7ORG: {
393                                        IBaseXhtml dt = IBaseXhtml.class.cast(theElement);
394                                        if (!dt.isEmpty()) {
395                                                // TODO: this is probably not as efficient as it could be
396                                                XhtmlDt hdt = new XhtmlDt();
397                                                hdt.setValueAsString(dt.getValueAsString());
398                                                encodeXhtml(hdt, theEventWriter);
399                                        }
400                                        break;
401                                }
402                                case EXTENSION_DECLARED:
403                                case UNDECL_EXT: {
404                                        throw new IllegalStateException(Msg.code(1853) + "state should not happen: " + childDef.getName());
405                                }
406                        }
407
408                        writeCommentsPost(theEventWriter, theElement);
409
410                } finally {
411                        theEncodeContext.popPath();
412                }
413        }
414
415        private void encodeCompositeElementToStreamWriter(
416                        IBaseResource theResource,
417                        IBase theElement,
418                        XMLStreamWriter theEventWriter,
419                        boolean theContainedResource,
420                        CompositeChildElement theParent,
421                        EncodeContext theEncodeContext)
422                        throws XMLStreamException, DataFormatException {
423
424                for (CompositeChildElement nextChildElem :
425                                super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) {
426
427                        BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
428
429                        if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) {
430                                /*
431                                 * XML encoding is a one-off for extensions. The URL element goes in an attribute
432                                 * instead of being encoded as a normal element, only for XML encoding
433                                 */
434                                continue;
435                        }
436
437                        if (nextChild instanceof RuntimeChildNarrativeDefinition) {
438                                Optional<IBase> narr = nextChild.getAccessor().getFirstValueOrNull(theElement);
439                                INarrativeGenerator gen = getContext().getNarrativeGenerator();
440                                if (gen != null && narr.isPresent() == false) {
441                                        gen.populateResourceNarrative(getContext(), theResource);
442                                }
443
444                                narr = nextChild.getAccessor().getFirstValueOrNull(theElement);
445                                if (narr.isPresent()) {
446                                        RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
447                                        String childName = nextChild.getChildNameByDatatype(child.getDatatype());
448                                        BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
449                                        encodeChildElementToStreamWriter(
450                                                        theResource,
451                                                        theEventWriter,
452                                                        nextChild,
453                                                        narr.get(),
454                                                        childName,
455                                                        type,
456                                                        null,
457                                                        theContainedResource,
458                                                        nextChildElem,
459                                                        theEncodeContext);
460                                        continue;
461                                }
462                        }
463
464                        if (nextChild instanceof RuntimeChildContainedResources) {
465                                encodeChildElementToStreamWriter(
466                                                theResource,
467                                                theEventWriter,
468                                                nextChild,
469                                                null,
470                                                nextChild.getChildNameByDatatype(null),
471                                                nextChild.getChildElementDefinitionByDatatype(null),
472                                                null,
473                                                theContainedResource,
474                                                nextChildElem,
475                                                theEncodeContext);
476                        } else {
477
478                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
479                                values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
480
481                                if (values == null || values.isEmpty()) {
482                                        continue;
483                                }
484                                for (IBase nextValue : values) {
485                                        if ((nextValue == null || nextValue.isEmpty())) {
486                                                continue;
487                                        }
488
489                                        BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
490                                        if (childNameAndDef == null) {
491                                                continue;
492                                        }
493
494                                        String childName = childNameAndDef.getChildName();
495                                        BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
496                                        String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
497
498                                        boolean isExtension = childName.equals("extension") || childName.equals("modifierExtension");
499                                        if (isExtension && nextValue instanceof IBaseExtension) {
500                                                IBaseExtension<?, ?> ext = (IBaseExtension<?, ?>) nextValue;
501                                                if (isBlank(ext.getUrl())) {
502                                                        ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName);
503                                                        getErrorHandler().missingRequiredElement(loc, "url");
504                                                }
505                                                if (ext.getValue() != null && ext.getExtension().size() > 0) {
506                                                        ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName);
507                                                        getErrorHandler().extensionContainsValueAndNestedExtensions(loc);
508                                                }
509                                        }
510
511                                        if (extensionUrl != null && isExtension == false) {
512                                                encodeExtension(
513                                                                theResource,
514                                                                theEventWriter,
515                                                                theContainedResource,
516                                                                nextChildElem,
517                                                                nextChild,
518                                                                nextValue,
519                                                                childName,
520                                                                extensionUrl,
521                                                                childDef,
522                                                                theEncodeContext);
523                                        } else if (nextChild instanceof RuntimeChildExtension) {
524                                                IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
525                                                if ((extension.getValue() == null
526                                                                || extension.getValue().isEmpty())) {
527                                                        if (extension.getExtension().isEmpty()) {
528                                                                continue;
529                                                        }
530                                                }
531                                                encodeChildElementToStreamWriter(
532                                                                theResource,
533                                                                theEventWriter,
534                                                                nextChild,
535                                                                nextValue,
536                                                                childName,
537                                                                childDef,
538                                                                getExtensionUrl(extension.getUrl()),
539                                                                theContainedResource,
540                                                                nextChildElem,
541                                                                theEncodeContext);
542                                        } else {
543                                                encodeChildElementToStreamWriter(
544                                                                theResource,
545                                                                theEventWriter,
546                                                                nextChild,
547                                                                nextValue,
548                                                                childName,
549                                                                childDef,
550                                                                extensionUrl,
551                                                                theContainedResource,
552                                                                nextChildElem,
553                                                                theEncodeContext);
554                                        }
555                                }
556                        }
557                }
558        }
559
560        private void encodeExtension(
561                        IBaseResource theResource,
562                        XMLStreamWriter theEventWriter,
563                        boolean theContainedResource,
564                        CompositeChildElement nextChildElem,
565                        BaseRuntimeChildDefinition nextChild,
566                        IBase nextValue,
567                        String childName,
568                        String extensionUrl,
569                        BaseRuntimeElementDefinition<?> childDef,
570                        EncodeContext theEncodeContext)
571                        throws XMLStreamException {
572                BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
573                if (extDef.isModifier()) {
574                        theEventWriter.writeStartElement("modifierExtension");
575                } else {
576                        theEventWriter.writeStartElement("extension");
577                }
578
579                String elementId = getCompositeElementId(nextValue);
580                if (isNotBlank(elementId)) {
581                        theEventWriter.writeAttribute("id", elementId);
582                }
583
584                if (isBlank(extensionUrl)) {
585                        ParseLocation loc = new ParseLocation(theEncodeContext.toString());
586                        getErrorHandler().missingRequiredElement(loc, "url");
587                } else {
588                        theEventWriter.writeAttribute("url", extensionUrl);
589                }
590
591                encodeChildElementToStreamWriter(
592                                theResource,
593                                theEventWriter,
594                                nextChild,
595                                nextValue,
596                                childName,
597                                childDef,
598                                null,
599                                theContainedResource,
600                                nextChildElem,
601                                theEncodeContext);
602                theEventWriter.writeEndElement();
603        }
604
605        private void encodeExtensionsIfPresent(
606                        IBaseResource theResource,
607                        XMLStreamWriter theWriter,
608                        IBase theElement,
609                        boolean theIncludedResource,
610                        EncodeContext theEncodeContext)
611                        throws XMLStreamException, DataFormatException {
612                if (theElement instanceof ISupportsUndeclaredExtensions) {
613                        ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
614                        encodeUndeclaredExtensions(
615                                        theResource,
616                                        theWriter,
617                                        toBaseExtensionList(res.getUndeclaredExtensions()),
618                                        "extension",
619                                        theIncludedResource,
620                                        theEncodeContext);
621                        encodeUndeclaredExtensions(
622                                        theResource,
623                                        theWriter,
624                                        toBaseExtensionList(res.getUndeclaredModifierExtensions()),
625                                        "modifierExtension",
626                                        theIncludedResource,
627                                        theEncodeContext);
628                }
629                if (theElement instanceof IBaseHasExtensions) {
630                        IBaseHasExtensions res = (IBaseHasExtensions) theElement;
631                        encodeUndeclaredExtensions(
632                                        theResource, theWriter, res.getExtension(), "extension", theIncludedResource, theEncodeContext);
633                }
634                if (theElement instanceof IBaseHasModifierExtensions) {
635                        IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
636                        encodeUndeclaredExtensions(
637                                        theResource,
638                                        theWriter,
639                                        res.getModifierExtension(),
640                                        "modifierExtension",
641                                        theIncludedResource,
642                                        theEncodeContext);
643                }
644        }
645
646        private void encodeResourceToXmlStreamWriter(
647                        IBaseResource theResource,
648                        XMLStreamWriter theEventWriter,
649                        boolean theIncludedResource,
650                        EncodeContext theEncodeContext)
651                        throws XMLStreamException, DataFormatException {
652                IIdType resourceId = null;
653
654                if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
655                        resourceId = theResource.getIdElement();
656                        if (theResource.getIdElement().getValue().startsWith("urn:")) {
657                                resourceId = null;
658                        }
659                }
660
661                if (!theIncludedResource) {
662                        if (super.shouldEncodeResourceId(theResource, theEncodeContext) == false) {
663                                resourceId = null;
664                        } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
665                                resourceId = getEncodeForceResourceId();
666                        }
667                }
668
669                encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId, theEncodeContext);
670        }
671
672        private void encodeResourceToXmlStreamWriter(
673                        IBaseResource theResource,
674                        XMLStreamWriter theEventWriter,
675                        boolean theContainedResource,
676                        IIdType theResourceId,
677                        EncodeContext theEncodeContext)
678                        throws XMLStreamException {
679                RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
680                if (resDef == null) {
681                        throw new ConfigurationException(Msg.code(1854) + "Unknown resource type: " + theResource.getClass());
682                }
683
684                if (!theContainedResource) {
685                        containResourcesInReferences(theResource);
686                }
687
688                theEventWriter.writeStartElement(resDef.getName());
689                theEventWriter.writeDefaultNamespace(FHIR_NS);
690
691                if (theResource instanceof IAnyResource) {
692                        // HL7.org Structures
693                        if (theResourceId != null) {
694                                writeCommentsPre(theEventWriter, theResourceId);
695                                theEventWriter.writeStartElement("id");
696                                theEventWriter.writeAttribute("value", theResourceId.getIdPart());
697                                encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
698                                theEventWriter.writeEndElement();
699                                writeCommentsPost(theEventWriter, theResourceId);
700                        }
701
702                        encodeCompositeElementToStreamWriter(
703                                        theResource,
704                                        theResource,
705                                        theEventWriter,
706                                        theContainedResource,
707                                        new CompositeChildElement(resDef, theEncodeContext),
708                                        theEncodeContext);
709
710                } else {
711
712                        // DSTU2+
713
714                        IResource resource = (IResource) theResource;
715                        if (theResourceId != null) {
716                                /*      writeCommentsPre(theEventWriter, theResourceId);
717                                        writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
718                                writeCommentsPost(theEventWriter, theResourceId);*/
719                                theEventWriter.writeStartElement("id");
720                                theEventWriter.writeAttribute("value", theResourceId.getIdPart());
721                                encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
722                                theEventWriter.writeEndElement();
723                                writeCommentsPost(theEventWriter, theResourceId);
724                        }
725
726                        InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
727                        IdDt resourceId = resource.getId();
728                        String versionIdPart = resourceId.getVersionIdPart();
729                        if (isBlank(versionIdPart)) {
730                                versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
731                        }
732                        List<BaseCodingDt> securityLabels =
733                                        extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
734                        List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
735                        profiles = super.getProfileTagsForEncoding(resource, profiles);
736
737                        TagList tags = getMetaTagsForEncoding((resource), theEncodeContext);
738
739                        if (super.shouldEncodeResourceMeta(resource, theEncodeContext)
740                                        && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
741                                theEventWriter.writeStartElement("meta");
742                                if (shouldEncodePath(resource, "meta.versionId", theEncodeContext)) {
743                                        writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart);
744                                }
745                                if (updated != null) {
746                                        if (shouldEncodePath(resource, "meta.lastUpdated", theEncodeContext)) {
747                                                writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
748                                        }
749                                }
750
751                                for (IIdType profile : profiles) {
752                                        theEventWriter.writeStartElement("profile");
753                                        theEventWriter.writeAttribute("value", profile.getValue());
754                                        theEventWriter.writeEndElement();
755                                }
756                                for (BaseCodingDt securityLabel : securityLabels) {
757                                        theEventWriter.writeStartElement("security");
758                                        encodeCompositeElementToStreamWriter(
759                                                        resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
760                                        theEventWriter.writeEndElement();
761                                }
762                                if (tags != null) {
763                                        for (Tag tag : tags) {
764                                                if (tag.isEmpty()) {
765                                                        continue;
766                                                }
767                                                theEventWriter.writeStartElement("tag");
768                                                writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme());
769                                                writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm());
770                                                writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel());
771                                                writeOptionalTagWithValue(theEventWriter, "version", tag.getVersion());
772                                                Boolean userSelected = tag.getUserSelectedBoolean();
773                                                if (userSelected != null) {
774                                                        writeOptionalTagWithValue(theEventWriter, "userSelected", userSelected.toString());
775                                                }
776                                                theEventWriter.writeEndElement();
777                                        }
778                                }
779                                theEventWriter.writeEndElement();
780                        }
781
782                        if (theResource instanceof IBaseBinary) {
783                                IBaseBinary bin = (IBaseBinary) theResource;
784                                writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
785                                writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
786                        } else {
787                                encodeCompositeElementToStreamWriter(
788                                                theResource,
789                                                theResource,
790                                                theEventWriter,
791                                                theContainedResource,
792                                                new CompositeChildElement(resDef, theEncodeContext),
793                                                theEncodeContext);
794                        }
795                }
796
797                theEventWriter.writeEndElement();
798        }
799
800        private void encodeUndeclaredExtensions(
801                        IBaseResource theResource,
802                        XMLStreamWriter theEventWriter,
803                        List<? extends IBaseExtension<?, ?>> theExtensions,
804                        String tagName,
805                        boolean theIncludedResource,
806                        EncodeContext theEncodeContext)
807                        throws XMLStreamException, DataFormatException {
808                for (IBaseExtension<?, ?> next : theExtensions) {
809                        if (next == null
810                                        || (ElementUtil.isEmpty(next.getValue())
811                                                        && next.getExtension().isEmpty())) {
812                                continue;
813                        }
814
815                        writeCommentsPre(theEventWriter, next);
816
817                        theEventWriter.writeStartElement(tagName);
818
819                        String elementId = getCompositeElementId(next);
820                        if (isNotBlank(elementId)) {
821                                theEventWriter.writeAttribute("id", elementId);
822                        }
823
824                        String url = getExtensionUrl(next.getUrl());
825                        if (isNotBlank(url)) {
826                                theEventWriter.writeAttribute("url", url);
827                        }
828
829                        if (next.getValue() != null) {
830                                IBaseDatatype value = next.getValue();
831                                RuntimeChildUndeclaredExtensionDefinition extDef =
832                                                getContext().getRuntimeChildUndeclaredExtensionDefinition();
833                                String childName = extDef.getChildNameByDatatype(value.getClass());
834                                BaseRuntimeElementDefinition<?> childDef;
835                                if (childName == null) {
836                                        childDef = getContext().getElementDefinition(value.getClass());
837                                        if (childDef == null) {
838                                                throw new ConfigurationException(
839                                                                Msg.code(1855) + "Unable to encode extension, unrecognized child element type: "
840                                                                                + value.getClass().getCanonicalName());
841                                        }
842                                        childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef);
843                                } else {
844                                        childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
845                                        if (childDef == null) {
846                                                throw new ConfigurationException(
847                                                                Msg.code(1856) + "Unable to encode extension, unrecognized child element type: "
848                                                                                + value.getClass().getCanonicalName());
849                                        }
850                                }
851                                encodeChildElementToStreamWriter(
852                                                theResource,
853                                                theEventWriter,
854                                                extDef,
855                                                value,
856                                                childName,
857                                                childDef,
858                                                null,
859                                                theIncludedResource,
860                                                null,
861                                                theEncodeContext);
862                        }
863
864                        // child extensions
865                        encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource, theEncodeContext);
866
867                        theEventWriter.writeEndElement();
868
869                        writeCommentsPost(theEventWriter, next);
870                }
871        }
872
873        private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
874                if (theDt == null || theDt.getValue() == null) {
875                        return;
876                }
877
878                List<XMLEvent> events = XmlUtil.parse(theDt.getValue());
879                boolean firstElement = true;
880
881                for (XMLEvent event : events) {
882                        switch (event.getEventType()) {
883                                case XMLStreamConstants.ATTRIBUTE:
884                                        Attribute attr = (Attribute) event;
885                                        if (isBlank(attr.getName().getPrefix())) {
886                                                if (isBlank(attr.getName().getNamespaceURI())) {
887                                                        theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue());
888                                                } else {
889                                                        theEventWriter.writeAttribute(
890                                                                        attr.getName().getNamespaceURI(),
891                                                                        attr.getName().getLocalPart(),
892                                                                        attr.getValue());
893                                                }
894                                        } else {
895                                                theEventWriter.writeAttribute(
896                                                                attr.getName().getPrefix(),
897                                                                attr.getName().getNamespaceURI(),
898                                                                attr.getName().getLocalPart(),
899                                                                attr.getValue());
900                                        }
901
902                                        break;
903                                case XMLStreamConstants.CDATA:
904                                        theEventWriter.writeCData(((Characters) event).getData());
905                                        break;
906                                case XMLStreamConstants.CHARACTERS:
907                                case XMLStreamConstants.SPACE:
908                                        String data = ((Characters) event).getData();
909                                        theEventWriter.writeCharacters(data);
910                                        break;
911                                case XMLStreamConstants.COMMENT:
912                                        theEventWriter.writeComment(((Comment) event).getText());
913                                        break;
914                                case XMLStreamConstants.END_ELEMENT:
915                                        theEventWriter.writeEndElement();
916                                        break;
917                                case XMLStreamConstants.ENTITY_REFERENCE:
918                                        EntityReference er = (EntityReference) event;
919                                        theEventWriter.writeEntityRef(er.getName());
920                                        break;
921                                case XMLStreamConstants.NAMESPACE:
922                                        Namespace ns = (Namespace) event;
923                                        theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI());
924                                        break;
925                                case XMLStreamConstants.START_ELEMENT:
926                                        StartElement se = event.asStartElement();
927                                        if (firstElement) {
928                                                if (StringUtils.isBlank(se.getName().getPrefix())) {
929                                                        String namespaceURI = se.getName().getNamespaceURI();
930                                                        if (StringUtils.isBlank(namespaceURI)) {
931                                                                namespaceURI = "http://www.w3.org/1999/xhtml";
932                                                        }
933                                                        theEventWriter.writeStartElement(se.getName().getLocalPart());
934                                                        theEventWriter.writeDefaultNamespace(namespaceURI);
935                                                } else {
936                                                        String prefix = se.getName().getPrefix();
937                                                        String namespaceURI = se.getName().getNamespaceURI();
938                                                        theEventWriter.writeStartElement(
939                                                                        prefix, se.getName().getLocalPart(), namespaceURI);
940                                                        theEventWriter.writeNamespace(prefix, namespaceURI);
941                                                }
942                                                firstElement = false;
943                                        } else {
944                                                if (isBlank(se.getName().getPrefix())) {
945                                                        if (isBlank(se.getName().getNamespaceURI())) {
946                                                                theEventWriter.writeStartElement(se.getName().getLocalPart());
947                                                        } else {
948                                                                if (StringUtils.isBlank(se.getName().getPrefix())) {
949                                                                        theEventWriter.writeStartElement(
950                                                                                        se.getName().getLocalPart());
951                                                                        // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
952                                                                } else {
953                                                                        theEventWriter.writeStartElement(
954                                                                                        se.getName().getNamespaceURI(),
955                                                                                        se.getName().getLocalPart());
956                                                                }
957                                                        }
958                                                } else {
959                                                        theEventWriter.writeStartElement(
960                                                                        se.getName().getPrefix(),
961                                                                        se.getName().getLocalPart(),
962                                                                        se.getName().getNamespaceURI());
963                                                }
964                                        }
965                                        for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext(); ) {
966                                                Attribute next = (Attribute) attrIter.next();
967                                                if (isBlank(next.getName().getNamespaceURI())) {
968                                                        theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue());
969                                                } else {
970                                                        theEventWriter.writeAttribute(
971                                                                        next.getName().getPrefix(),
972                                                                        next.getName().getNamespaceURI(),
973                                                                        next.getName().getLocalPart(),
974                                                                        next.getValue());
975                                                }
976                                        }
977                                        break;
978                                case XMLStreamConstants.DTD:
979                                case XMLStreamConstants.END_DOCUMENT:
980                                case XMLStreamConstants.ENTITY_DECLARATION:
981                                case XMLStreamConstants.NOTATION_DECLARATION:
982                                case XMLStreamConstants.PROCESSING_INSTRUCTION:
983                                case XMLStreamConstants.START_DOCUMENT:
984                                        break;
985                        }
986                }
987        }
988
989        @Override
990        public EncodingEnum getEncoding() {
991                return EncodingEnum.XML;
992        }
993
994        private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
995                ParserState<T> parserState =
996                                ParserState.getPreResourceInstance(this, theResourceType, getContext(), false, getErrorHandler());
997                return doXmlLoop(theStreamReader, parserState);
998        }
999
1000        @Override
1001        public IParser setPrettyPrint(boolean thePrettyPrint) {
1002                myPrettyPrint = thePrettyPrint;
1003                return this;
1004        }
1005
1006        /**
1007         * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to
1008         * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be
1009         * rejected by the compiler some of the time.
1010         */
1011        private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) {
1012                List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size());
1013                retVal.addAll(theList);
1014                return retVal;
1015        }
1016
1017        private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
1018                if (theElement != null && theElement.hasFormatComment()) {
1019                        for (String next : theElement.getFormatCommentsPost()) {
1020                                if (isNotBlank(next)) {
1021                                        theEventWriter.writeComment(next);
1022                                }
1023                        }
1024                }
1025        }
1026
1027        private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
1028                if (theElement != null && theElement.hasFormatComment()) {
1029                        for (String next : theElement.getFormatCommentsPre()) {
1030                                if (isNotBlank(next)) {
1031                                        theEventWriter.writeComment(next);
1032                                }
1033                        }
1034                }
1035        }
1036
1037        private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue)
1038                        throws XMLStreamException {
1039                if (StringUtils.isNotBlank(theValue)) {
1040                        theEventWriter.writeStartElement(theName);
1041                        theEventWriter.writeAttribute("value", theValue);
1042                        theEventWriter.writeEndElement();
1043                }
1044        }
1045}