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