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