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