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