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