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, theEncodeContext)) {
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 :
363                                                        theEncodeContext.getContainedResources().getContainedResources()) {
364                                                IIdType resourceId =
365                                                                theEncodeContext.getContainedResources().getResourceId(next);
366                                                theEventWriter.writeStartElement("contained");
367                                                String value = resourceId.getValue();
368                                                encodeResourceToXmlStreamWriter(
369                                                                next, theEventWriter, true, fixContainedResourceId(value), theEncodeContext);
370                                                theEventWriter.writeEndElement();
371                                        }
372                                        break;
373                                }
374                                case RESOURCE: {
375                                        IBaseResource resource = (IBaseResource) theElement;
376                                        String resourceName = getContext().getResourceType(resource);
377                                        if (!super.shouldEncodeResource(resourceName, theEncodeContext)) {
378                                                break;
379                                        }
380                                        theEventWriter.writeStartElement(theChildName);
381                                        theEncodeContext.pushPath(resourceName, true);
382                                        encodeResourceToXmlStreamWriter(resource, theEventWriter, theIncludedResource, theEncodeContext);
383                                        theEncodeContext.popPath();
384                                        theEventWriter.writeEndElement();
385                                        break;
386                                }
387                                case PRIMITIVE_XHTML: {
388                                        XhtmlDt dt = XhtmlDt.class.cast(theElement);
389                                        if (dt.hasContent()) {
390                                                encodeXhtml(dt, theEventWriter);
391                                        }
392                                        break;
393                                }
394                                case PRIMITIVE_XHTML_HL7ORG: {
395                                        IBaseXhtml dt = IBaseXhtml.class.cast(theElement);
396                                        if (!dt.isEmpty()) {
397                                                // TODO: this is probably not as efficient as it could be
398                                                XhtmlDt hdt = new XhtmlDt();
399                                                hdt.setValueAsString(dt.getValueAsString());
400                                                encodeXhtml(hdt, theEventWriter);
401                                        }
402                                        break;
403                                }
404                                case EXTENSION_DECLARED:
405                                case UNDECL_EXT: {
406                                        throw new IllegalStateException(Msg.code(1853) + "state should not happen: " + childDef.getName());
407                                }
408                        }
409
410                        writeCommentsPost(theEventWriter, theElement);
411
412                } finally {
413                        theEncodeContext.popPath();
414                }
415        }
416
417        private void encodeCompositeElementToStreamWriter(
418                        IBaseResource theResource,
419                        IBase theElement,
420                        XMLStreamWriter theEventWriter,
421                        boolean theContainedResource,
422                        CompositeChildElement theParent,
423                        EncodeContext theEncodeContext)
424                        throws XMLStreamException, DataFormatException {
425
426                for (CompositeChildElement nextChildElem :
427                                super.compositeChildIterator(theElement, theContainedResource, theParent, theEncodeContext)) {
428
429                        BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
430
431                        if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) {
432                                /*
433                                 * XML encoding is a one-off for extensions. The URL element goes in an attribute
434                                 * instead of being encoded as a normal element, only for XML encoding
435                                 */
436                                continue;
437                        }
438
439                        if (nextChild instanceof RuntimeChildNarrativeDefinition) {
440                                Optional<IBase> narr = nextChild.getAccessor().getFirstValueOrNull(theElement);
441                                INarrativeGenerator gen = getContext().getNarrativeGenerator();
442                                if (gen != null && narr.isPresent() == false) {
443                                        gen.populateResourceNarrative(getContext(), theResource);
444                                }
445
446                                narr = nextChild.getAccessor().getFirstValueOrNull(theElement);
447                                if (narr.isPresent()) {
448                                        RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
449                                        String childName = nextChild.getChildNameByDatatype(child.getDatatype());
450                                        BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
451                                        encodeChildElementToStreamWriter(
452                                                        theResource,
453                                                        theEventWriter,
454                                                        nextChild,
455                                                        narr.get(),
456                                                        childName,
457                                                        type,
458                                                        null,
459                                                        theContainedResource,
460                                                        nextChildElem,
461                                                        theEncodeContext);
462                                        continue;
463                                }
464                        }
465
466                        if (nextChild instanceof RuntimeChildContainedResources) {
467                                encodeChildElementToStreamWriter(
468                                                theResource,
469                                                theEventWriter,
470                                                nextChild,
471                                                null,
472                                                nextChild.getChildNameByDatatype(null),
473                                                nextChild.getChildElementDefinitionByDatatype(null),
474                                                null,
475                                                theContainedResource,
476                                                nextChildElem,
477                                                theEncodeContext);
478                        } else {
479
480                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
481                                values = preProcessValues(nextChild, theResource, values, nextChildElem, theEncodeContext);
482
483                                if (values == null || values.isEmpty()) {
484                                        continue;
485                                }
486                                for (IBase nextValue : values) {
487                                        if ((nextValue == null || nextValue.isEmpty())) {
488                                                continue;
489                                        }
490
491                                        BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
492                                        if (childNameAndDef == null) {
493                                                continue;
494                                        }
495
496                                        String childName = childNameAndDef.getChildName();
497                                        BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
498                                        String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
499
500                                        boolean isExtension = childName.equals("extension") || childName.equals("modifierExtension");
501                                        if (isExtension && nextValue instanceof IBaseExtension) {
502                                                IBaseExtension<?, ?> ext = (IBaseExtension<?, ?>) nextValue;
503                                                if (isBlank(ext.getUrl())) {
504                                                        ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName);
505                                                        getErrorHandler().missingRequiredElement(loc, "url");
506                                                }
507                                                if (ext.getValue() != null && ext.getExtension().size() > 0) {
508                                                        ParseLocation loc = new ParseLocation(theEncodeContext.toString() + "." + childName);
509                                                        getErrorHandler().extensionContainsValueAndNestedExtensions(loc);
510                                                }
511                                        }
512
513                                        if (extensionUrl != null && isExtension == false) {
514                                                encodeExtension(
515                                                                theResource,
516                                                                theEventWriter,
517                                                                theContainedResource,
518                                                                nextChildElem,
519                                                                nextChild,
520                                                                nextValue,
521                                                                childName,
522                                                                extensionUrl,
523                                                                childDef,
524                                                                theEncodeContext);
525                                        } else if (nextChild instanceof RuntimeChildExtension) {
526                                                IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
527                                                if ((extension.getValue() == null
528                                                                || extension.getValue().isEmpty())) {
529                                                        if (extension.getExtension().isEmpty()) {
530                                                                continue;
531                                                        }
532                                                }
533                                                encodeChildElementToStreamWriter(
534                                                                theResource,
535                                                                theEventWriter,
536                                                                nextChild,
537                                                                nextValue,
538                                                                childName,
539                                                                childDef,
540                                                                getExtensionUrl(extension.getUrl()),
541                                                                theContainedResource,
542                                                                nextChildElem,
543                                                                theEncodeContext);
544                                        } else {
545                                                encodeChildElementToStreamWriter(
546                                                                theResource,
547                                                                theEventWriter,
548                                                                nextChild,
549                                                                nextValue,
550                                                                childName,
551                                                                childDef,
552                                                                extensionUrl,
553                                                                theContainedResource,
554                                                                nextChildElem,
555                                                                theEncodeContext);
556                                        }
557                                }
558                        }
559                }
560        }
561
562        private void encodeExtension(
563                        IBaseResource theResource,
564                        XMLStreamWriter theEventWriter,
565                        boolean theContainedResource,
566                        CompositeChildElement nextChildElem,
567                        BaseRuntimeChildDefinition nextChild,
568                        IBase nextValue,
569                        String childName,
570                        String extensionUrl,
571                        BaseRuntimeElementDefinition<?> childDef,
572                        EncodeContext theEncodeContext)
573                        throws XMLStreamException {
574                BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
575                if (extDef.isModifier()) {
576                        theEventWriter.writeStartElement("modifierExtension");
577                } else {
578                        theEventWriter.writeStartElement("extension");
579                }
580
581                String elementId = getCompositeElementId(nextValue);
582                if (isNotBlank(elementId)) {
583                        theEventWriter.writeAttribute("id", elementId);
584                }
585
586                if (isBlank(extensionUrl)) {
587                        ParseLocation loc = new ParseLocation(theEncodeContext.toString());
588                        getErrorHandler().missingRequiredElement(loc, "url");
589                } else {
590                        theEventWriter.writeAttribute("url", extensionUrl);
591                }
592
593                encodeChildElementToStreamWriter(
594                                theResource,
595                                theEventWriter,
596                                nextChild,
597                                nextValue,
598                                childName,
599                                childDef,
600                                null,
601                                theContainedResource,
602                                nextChildElem,
603                                theEncodeContext);
604                theEventWriter.writeEndElement();
605        }
606
607        private void encodeExtensionsIfPresent(
608                        IBaseResource theResource,
609                        XMLStreamWriter theWriter,
610                        IBase theElement,
611                        boolean theIncludedResource,
612                        EncodeContext theEncodeContext)
613                        throws XMLStreamException, DataFormatException {
614                if (theElement instanceof ISupportsUndeclaredExtensions) {
615                        ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
616                        encodeUndeclaredExtensions(
617                                        theResource,
618                                        theWriter,
619                                        toBaseExtensionList(res.getUndeclaredExtensions()),
620                                        "extension",
621                                        theIncludedResource,
622                                        theEncodeContext);
623                        encodeUndeclaredExtensions(
624                                        theResource,
625                                        theWriter,
626                                        toBaseExtensionList(res.getUndeclaredModifierExtensions()),
627                                        "modifierExtension",
628                                        theIncludedResource,
629                                        theEncodeContext);
630                }
631                if (theElement instanceof IBaseHasExtensions) {
632                        IBaseHasExtensions res = (IBaseHasExtensions) theElement;
633                        encodeUndeclaredExtensions(
634                                        theResource, theWriter, res.getExtension(), "extension", theIncludedResource, theEncodeContext);
635                }
636                if (theElement instanceof IBaseHasModifierExtensions) {
637                        IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
638                        encodeUndeclaredExtensions(
639                                        theResource,
640                                        theWriter,
641                                        res.getModifierExtension(),
642                                        "modifierExtension",
643                                        theIncludedResource,
644                                        theEncodeContext);
645                }
646        }
647
648        private void encodeResourceToXmlStreamWriter(
649                        IBaseResource theResource,
650                        XMLStreamWriter theEventWriter,
651                        boolean theIncludedResource,
652                        EncodeContext theEncodeContext)
653                        throws XMLStreamException, DataFormatException {
654                IIdType resourceId = null;
655
656                if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
657                        resourceId = theResource.getIdElement();
658                        if (theResource.getIdElement().getValue().startsWith("urn:")) {
659                                resourceId = null;
660                        }
661                }
662
663                if (!theIncludedResource) {
664                        if (super.shouldEncodeResourceId(theResource, theEncodeContext) == false) {
665                                resourceId = null;
666                        } else if (theEncodeContext.getResourcePath().size() == 1 && getEncodeForceResourceId() != null) {
667                                resourceId = getEncodeForceResourceId();
668                        }
669                }
670
671                encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId, theEncodeContext);
672        }
673
674        private void encodeResourceToXmlStreamWriter(
675                        IBaseResource theResource,
676                        XMLStreamWriter theEventWriter,
677                        boolean theContainedResource,
678                        IIdType theResourceId,
679                        EncodeContext theEncodeContext)
680                        throws XMLStreamException {
681                RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
682                if (resDef == null) {
683                        throw new ConfigurationException(Msg.code(1854) + "Unknown resource type: " + theResource.getClass());
684                }
685
686                if (!theContainedResource) {
687                        containResourcesInReferences(theResource, theEncodeContext);
688                }
689
690                theEventWriter.writeStartElement(resDef.getName());
691                theEventWriter.writeDefaultNamespace(FHIR_NS);
692
693                if (theResource instanceof IAnyResource) {
694                        // HL7.org Structures
695                        if (theResourceId != null) {
696                                writeCommentsPre(theEventWriter, theResourceId);
697                                theEventWriter.writeStartElement("id");
698                                theEventWriter.writeAttribute("value", theResourceId.getIdPart());
699                                encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
700                                theEventWriter.writeEndElement();
701                                writeCommentsPost(theEventWriter, theResourceId);
702                        }
703
704                        encodeCompositeElementToStreamWriter(
705                                        theResource,
706                                        theResource,
707                                        theEventWriter,
708                                        theContainedResource,
709                                        new CompositeChildElement(resDef, theEncodeContext),
710                                        theEncodeContext);
711
712                } else {
713
714                        // DSTU2+
715
716                        IResource resource = (IResource) theResource;
717                        if (theResourceId != null) {
718                                /*      writeCommentsPre(theEventWriter, theResourceId);
719                                        writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
720                                writeCommentsPost(theEventWriter, theResourceId);*/
721                                theEventWriter.writeStartElement("id");
722                                theEventWriter.writeAttribute("value", theResourceId.getIdPart());
723                                encodeExtensionsIfPresent(theResource, theEventWriter, theResourceId, false, theEncodeContext);
724                                theEventWriter.writeEndElement();
725                                writeCommentsPost(theEventWriter, theResourceId);
726                        }
727
728                        InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
729                        IdDt resourceId = resource.getId();
730                        String versionIdPart = resourceId.getVersionIdPart();
731                        if (isBlank(versionIdPart)) {
732                                versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
733                        }
734                        List<BaseCodingDt> securityLabels =
735                                        extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
736                        List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
737                        profiles = super.getProfileTagsForEncoding(resource, profiles);
738
739                        TagList tags = getMetaTagsForEncoding((resource), theEncodeContext);
740
741                        if (super.shouldEncodeResourceMeta(resource, theEncodeContext)
742                                        && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
743                                theEventWriter.writeStartElement("meta");
744                                if (shouldEncodePath(resource, "meta.versionId", theEncodeContext)) {
745                                        writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart);
746                                }
747                                if (updated != null) {
748                                        if (shouldEncodePath(resource, "meta.lastUpdated", theEncodeContext)) {
749                                                writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
750                                        }
751                                }
752
753                                for (IIdType profile : profiles) {
754                                        theEventWriter.writeStartElement("profile");
755                                        theEventWriter.writeAttribute("value", profile.getValue());
756                                        theEventWriter.writeEndElement();
757                                }
758                                for (BaseCodingDt securityLabel : securityLabels) {
759                                        theEventWriter.writeStartElement("security");
760                                        encodeCompositeElementToStreamWriter(
761                                                        resource, securityLabel, theEventWriter, theContainedResource, null, theEncodeContext);
762                                        theEventWriter.writeEndElement();
763                                }
764                                if (tags != null) {
765                                        for (Tag tag : tags) {
766                                                if (tag.isEmpty()) {
767                                                        continue;
768                                                }
769                                                theEventWriter.writeStartElement("tag");
770                                                writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme());
771                                                writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm());
772                                                writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel());
773                                                writeOptionalTagWithValue(theEventWriter, "version", tag.getVersion());
774                                                Boolean userSelected = tag.getUserSelectedBoolean();
775                                                if (userSelected != null) {
776                                                        writeOptionalTagWithValue(theEventWriter, "userSelected", userSelected.toString());
777                                                }
778                                                theEventWriter.writeEndElement();
779                                        }
780                                }
781                                theEventWriter.writeEndElement();
782                        }
783
784                        if (theResource instanceof IBaseBinary) {
785                                IBaseBinary bin = (IBaseBinary) theResource;
786                                writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
787                                writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
788                        } else {
789                                encodeCompositeElementToStreamWriter(
790                                                theResource,
791                                                theResource,
792                                                theEventWriter,
793                                                theContainedResource,
794                                                new CompositeChildElement(resDef, theEncodeContext),
795                                                theEncodeContext);
796                        }
797                }
798
799                theEventWriter.writeEndElement();
800        }
801
802        private void encodeUndeclaredExtensions(
803                        IBaseResource theResource,
804                        XMLStreamWriter theEventWriter,
805                        List<? extends IBaseExtension<?, ?>> theExtensions,
806                        String tagName,
807                        boolean theIncludedResource,
808                        EncodeContext theEncodeContext)
809                        throws XMLStreamException, DataFormatException {
810                for (IBaseExtension<?, ?> next : theExtensions) {
811                        if (next == null
812                                        || (ElementUtil.isEmpty(next.getValue())
813                                                        && next.getExtension().isEmpty())) {
814                                continue;
815                        }
816
817                        writeCommentsPre(theEventWriter, next);
818
819                        theEventWriter.writeStartElement(tagName);
820
821                        String elementId = getCompositeElementId(next);
822                        if (isNotBlank(elementId)) {
823                                theEventWriter.writeAttribute("id", elementId);
824                        }
825
826                        String url = getExtensionUrl(next.getUrl());
827                        if (isNotBlank(url)) {
828                                theEventWriter.writeAttribute("url", url);
829                        }
830
831                        if (next.getValue() != null) {
832                                IBaseDatatype value = next.getValue();
833                                RuntimeChildUndeclaredExtensionDefinition extDef =
834                                                getContext().getRuntimeChildUndeclaredExtensionDefinition();
835                                String childName = extDef.getChildNameByDatatype(value.getClass());
836                                BaseRuntimeElementDefinition<?> childDef;
837                                if (childName == null) {
838                                        childDef = getContext().getElementDefinition(value.getClass());
839                                        if (childDef == null) {
840                                                throw new ConfigurationException(
841                                                                Msg.code(1855) + "Unable to encode extension, unrecognized child element type: "
842                                                                                + value.getClass().getCanonicalName());
843                                        }
844                                        childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef);
845                                } else {
846                                        childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
847                                        if (childDef == null) {
848                                                throw new ConfigurationException(
849                                                                Msg.code(1856) + "Unable to encode extension, unrecognized child element type: "
850                                                                                + value.getClass().getCanonicalName());
851                                        }
852                                }
853                                encodeChildElementToStreamWriter(
854                                                theResource,
855                                                theEventWriter,
856                                                extDef,
857                                                value,
858                                                childName,
859                                                childDef,
860                                                null,
861                                                theIncludedResource,
862                                                null,
863                                                theEncodeContext);
864                        }
865
866                        // child extensions
867                        encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource, theEncodeContext);
868
869                        theEventWriter.writeEndElement();
870
871                        writeCommentsPost(theEventWriter, next);
872                }
873        }
874
875        private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
876                if (theDt == null || theDt.getValue() == null) {
877                        return;
878                }
879
880                List<XMLEvent> events = XmlUtil.parse(theDt.getValue());
881                boolean firstElement = true;
882
883                for (XMLEvent event : events) {
884                        switch (event.getEventType()) {
885                                case XMLStreamConstants.ATTRIBUTE:
886                                        Attribute attr = (Attribute) event;
887                                        if (isBlank(attr.getName().getPrefix())) {
888                                                if (isBlank(attr.getName().getNamespaceURI())) {
889                                                        theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue());
890                                                } else {
891                                                        theEventWriter.writeAttribute(
892                                                                        attr.getName().getNamespaceURI(),
893                                                                        attr.getName().getLocalPart(),
894                                                                        attr.getValue());
895                                                }
896                                        } else {
897                                                theEventWriter.writeAttribute(
898                                                                attr.getName().getPrefix(),
899                                                                attr.getName().getNamespaceURI(),
900                                                                attr.getName().getLocalPart(),
901                                                                attr.getValue());
902                                        }
903
904                                        break;
905                                case XMLStreamConstants.CDATA:
906                                        theEventWriter.writeCData(((Characters) event).getData());
907                                        break;
908                                case XMLStreamConstants.CHARACTERS:
909                                case XMLStreamConstants.SPACE:
910                                        String data = ((Characters) event).getData();
911                                        theEventWriter.writeCharacters(data);
912                                        break;
913                                case XMLStreamConstants.COMMENT:
914                                        theEventWriter.writeComment(((Comment) event).getText());
915                                        break;
916                                case XMLStreamConstants.END_ELEMENT:
917                                        theEventWriter.writeEndElement();
918                                        break;
919                                case XMLStreamConstants.ENTITY_REFERENCE:
920                                        EntityReference er = (EntityReference) event;
921                                        theEventWriter.writeEntityRef(er.getName());
922                                        break;
923                                case XMLStreamConstants.NAMESPACE:
924                                        Namespace ns = (Namespace) event;
925                                        theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI());
926                                        break;
927                                case XMLStreamConstants.START_ELEMENT:
928                                        StartElement se = event.asStartElement();
929                                        if (firstElement) {
930                                                if (StringUtils.isBlank(se.getName().getPrefix())) {
931                                                        String namespaceURI = se.getName().getNamespaceURI();
932                                                        if (StringUtils.isBlank(namespaceURI)) {
933                                                                namespaceURI = "http://www.w3.org/1999/xhtml";
934                                                        }
935                                                        theEventWriter.writeStartElement(se.getName().getLocalPart());
936                                                        theEventWriter.writeDefaultNamespace(namespaceURI);
937                                                } else {
938                                                        String prefix = se.getName().getPrefix();
939                                                        String namespaceURI = se.getName().getNamespaceURI();
940                                                        theEventWriter.writeStartElement(
941                                                                        prefix, se.getName().getLocalPart(), namespaceURI);
942                                                        theEventWriter.writeNamespace(prefix, namespaceURI);
943                                                }
944                                                firstElement = false;
945                                        } else {
946                                                if (isBlank(se.getName().getPrefix())) {
947                                                        if (isBlank(se.getName().getNamespaceURI())) {
948                                                                theEventWriter.writeStartElement(se.getName().getLocalPart());
949                                                        } else {
950                                                                if (StringUtils.isBlank(se.getName().getPrefix())) {
951                                                                        theEventWriter.writeStartElement(
952                                                                                        se.getName().getLocalPart());
953                                                                        // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
954                                                                } else {
955                                                                        theEventWriter.writeStartElement(
956                                                                                        se.getName().getNamespaceURI(),
957                                                                                        se.getName().getLocalPart());
958                                                                }
959                                                        }
960                                                } else {
961                                                        theEventWriter.writeStartElement(
962                                                                        se.getName().getPrefix(),
963                                                                        se.getName().getLocalPart(),
964                                                                        se.getName().getNamespaceURI());
965                                                }
966                                        }
967                                        for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext(); ) {
968                                                Attribute next = (Attribute) attrIter.next();
969                                                if (isBlank(next.getName().getNamespaceURI())) {
970                                                        theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue());
971                                                } else {
972                                                        theEventWriter.writeAttribute(
973                                                                        next.getName().getPrefix(),
974                                                                        next.getName().getNamespaceURI(),
975                                                                        next.getName().getLocalPart(),
976                                                                        next.getValue());
977                                                }
978                                        }
979                                        break;
980                                case XMLStreamConstants.DTD:
981                                case XMLStreamConstants.END_DOCUMENT:
982                                case XMLStreamConstants.ENTITY_DECLARATION:
983                                case XMLStreamConstants.NOTATION_DECLARATION:
984                                case XMLStreamConstants.PROCESSING_INSTRUCTION:
985                                case XMLStreamConstants.START_DOCUMENT:
986                                        break;
987                        }
988                }
989        }
990
991        @Override
992        public EncodingEnum getEncoding() {
993                return EncodingEnum.XML;
994        }
995
996        private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
997                ParserState<T> parserState =
998                                ParserState.getPreResourceInstance(this, theResourceType, getContext(), false, getErrorHandler());
999                return doXmlLoop(theStreamReader, parserState);
1000        }
1001
1002        @Override
1003        public IParser setPrettyPrint(boolean thePrettyPrint) {
1004                myPrettyPrint = thePrettyPrint;
1005                return this;
1006        }
1007
1008        /**
1009         * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to
1010         * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be
1011         * rejected by the compiler some of the time.
1012         */
1013        private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) {
1014                List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size());
1015                retVal.addAll(theList);
1016                return retVal;
1017        }
1018
1019        private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
1020                if (theElement != null && theElement.hasFormatComment()) {
1021                        for (String next : theElement.getFormatCommentsPost()) {
1022                                if (isNotBlank(next)) {
1023                                        theEventWriter.writeComment(next);
1024                                }
1025                        }
1026                }
1027        }
1028
1029        private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
1030                if (theElement != null && theElement.hasFormatComment()) {
1031                        for (String next : theElement.getFormatCommentsPre()) {
1032                                if (isNotBlank(next)) {
1033                                        theEventWriter.writeComment(next);
1034                                }
1035                        }
1036                }
1037        }
1038
1039        private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue)
1040                        throws XMLStreamException {
1041                if (StringUtils.isNotBlank(theValue)) {
1042                        theEventWriter.writeStartElement(theName);
1043                        theEventWriter.writeAttribute("value", theValue);
1044                        theEventWriter.writeEndElement();
1045                }
1046        }
1047}