001package ca.uhn.fhir.parser;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 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 */
022import static org.apache.commons.lang3.StringUtils.defaultString;
023import static org.apache.commons.lang3.StringUtils.isBlank;
024import static org.apache.commons.lang3.StringUtils.isNotBlank;
025
026import java.io.IOException;
027import java.io.Reader;
028import java.io.StringWriter;
029import java.io.Writer;
030import java.util.ArrayList;
031import java.util.Iterator;
032import java.util.List;
033
034import javax.xml.namespace.QName;
035import javax.xml.stream.FactoryConfigurationError;
036import javax.xml.stream.XMLEventReader;
037import javax.xml.stream.XMLStreamConstants;
038import javax.xml.stream.XMLStreamException;
039import javax.xml.stream.XMLStreamWriter;
040import javax.xml.stream.events.Attribute;
041import javax.xml.stream.events.Characters;
042import javax.xml.stream.events.Comment;
043import javax.xml.stream.events.EntityReference;
044import javax.xml.stream.events.Namespace;
045import javax.xml.stream.events.StartElement;
046import javax.xml.stream.events.XMLEvent;
047
048import org.apache.commons.lang3.StringUtils;
049import org.hl7.fhir.instance.model.api.IAnyResource;
050import org.hl7.fhir.instance.model.api.IBase;
051import org.hl7.fhir.instance.model.api.IBaseBinary;
052import org.hl7.fhir.instance.model.api.IBaseDatatype;
053import org.hl7.fhir.instance.model.api.IBaseExtension;
054import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
055import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
056import org.hl7.fhir.instance.model.api.IBaseResource;
057import org.hl7.fhir.instance.model.api.IBaseXhtml;
058import org.hl7.fhir.instance.model.api.IDomainResource;
059import org.hl7.fhir.instance.model.api.IIdType;
060import org.hl7.fhir.instance.model.api.INarrative;
061import org.hl7.fhir.instance.model.api.IPrimitiveType;
062
063import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
064import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
065import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
066import ca.uhn.fhir.context.ConfigurationException;
067import ca.uhn.fhir.context.FhirContext;
068import ca.uhn.fhir.context.FhirVersionEnum;
069import ca.uhn.fhir.context.RuntimeChildContainedResources;
070import ca.uhn.fhir.context.RuntimeChildExtension;
071import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
072import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
073import ca.uhn.fhir.context.RuntimeResourceDefinition;
074import ca.uhn.fhir.model.api.BaseBundle;
075import ca.uhn.fhir.model.api.Bundle;
076import ca.uhn.fhir.model.api.BundleEntry;
077import ca.uhn.fhir.model.api.IResource;
078import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
079import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
080import ca.uhn.fhir.model.api.Tag;
081import ca.uhn.fhir.model.api.TagList;
082import ca.uhn.fhir.model.base.composite.BaseCodingDt;
083import ca.uhn.fhir.model.primitive.IdDt;
084import ca.uhn.fhir.model.primitive.InstantDt;
085import ca.uhn.fhir.model.primitive.StringDt;
086import ca.uhn.fhir.model.primitive.XhtmlDt;
087import ca.uhn.fhir.narrative.INarrativeGenerator;
088import ca.uhn.fhir.rest.server.Constants;
089import ca.uhn.fhir.rest.server.EncodingEnum;
090import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
091import ca.uhn.fhir.util.ElementUtil;
092import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper;
093import ca.uhn.fhir.util.PrettyPrintWriterWrapper;
094import ca.uhn.fhir.util.XmlUtil;
095
096/**
097 * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use
098 * {@link FhirContext#newXmlParser()} to get an instance.
099 */
100public class XmlParser extends BaseParser /*implements IParser */{
101
102        static final String ATOM_NS = "http://www.w3.org/2005/Atom";
103        static final String FHIR_NS = "http://hl7.org/fhir";
104        static final String OPENSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1/";
105        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParser.class);
106        static final String RESREF_DISPLAY = "display";
107        static final String RESREF_REFERENCE = "reference";
108        static final String TOMBSTONES_NS = "http://purl.org/atompub/tombstones/1.0";
109        static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
110
111        // private static final Set<String> RESOURCE_NAMESPACES;
112
113        private FhirContext myContext;
114        private boolean myPrettyPrint;
115
116        /**
117         * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke
118         * {@link FhirContext#newXmlParser()}.
119         * 
120         * @param theParserErrorHandler
121         */
122        public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
123                super(theContext, theParserErrorHandler);
124                myContext = theContext;
125        }
126
127        private XMLEventReader createStreamReader(Reader theReader) {
128                try {
129                        return XmlUtil.createXmlReader(theReader);
130                } catch (FactoryConfigurationError e1) {
131                        throw new ConfigurationException("Failed to initialize STaX event factory", e1);
132                } catch (XMLStreamException e1) {
133                        throw new DataFormatException(e1);
134                }
135        }
136
137        private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException {
138                XMLStreamWriter eventWriter;
139                eventWriter = XmlUtil.createXmlStreamWriter(theWriter);
140                eventWriter = decorateStreamWriter(eventWriter);
141                return eventWriter;
142        }
143
144        private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
145                if (myPrettyPrint) {
146                        PrettyPrintWriterWrapper retVal = new PrettyPrintWriterWrapper(eventWriter);
147                        return retVal;
148                }
149                NonPrettyPrintWriterWrapper retVal = new NonPrettyPrintWriterWrapper(eventWriter);
150                return retVal;
151        }
152
153        @Override
154        public void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException {
155                try {
156                        XMLStreamWriter eventWriter = createXmlWriter(theWriter);
157                        if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
158                                encodeBundleToWriterDstu2(theBundle, eventWriter);
159                        } else {
160                                encodeBundleToWriterDstu1(theBundle, eventWriter);
161                        }
162                } catch (XMLStreamException e) {
163                        throw new ConfigurationException("Failed to initialize STaX event factory", e);
164                }
165        }
166
167        @Override
168        public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException {
169                XMLStreamWriter eventWriter;
170                try {
171                        eventWriter = createXmlWriter(theWriter);
172
173                        encodeResourceToXmlStreamWriter(theResource, eventWriter, false, false);
174                        eventWriter.flush();
175                } catch (XMLStreamException e) {
176                        throw new ConfigurationException("Failed to initialize STaX event factory", e);
177                }
178        }
179
180        @Override
181        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
182                XMLEventReader streamReader = createStreamReader(theReader);
183                return parseResource(theResourceType, streamReader);
184        }
185
186        private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) {
187                ourLog.trace("Entering XML parsing loop with state: {}", parserState);
188
189                try {
190                        List<String> heldComments = new ArrayList<String>(1);
191
192                        while (streamReader.hasNext()) {
193                                XMLEvent nextEvent = streamReader.nextEvent();
194                                try {
195
196                                        switch (nextEvent.getEventType()) {
197                                        case XMLStreamConstants.START_ELEMENT: {
198                                                StartElement elem = nextEvent.asStartElement();
199
200                                                String namespaceURI = elem.getName().getNamespaceURI();
201
202                                                if ("extension".equals(elem.getName().getLocalPart())) {
203                                                        Attribute urlAttr = elem.getAttributeByName(new QName("url"));
204                                                        String url;
205                                                        if (urlAttr == null || isBlank(urlAttr.getValue())) {
206                                                                getErrorHandler().missingRequiredElement(new ParseLocation("extension"), "url");
207                                                                url = null;
208                                                        } else {
209                                                                url = urlAttr.getValue();
210                                                        }
211                                                        parserState.enteringNewElementExtension(elem, url, false, getServerBaseUrl());
212                                                } else if ("modifierExtension".equals(elem.getName().getLocalPart())) {
213                                                        Attribute urlAttr = elem.getAttributeByName(new QName("url"));
214                                                        String url;
215                                                        if (urlAttr == null || isBlank(urlAttr.getValue())) {
216                                                                getErrorHandler().missingRequiredElement(new ParseLocation("modifierExtension"), "url");
217                                                                url = null;
218                                                        } else {
219                                                                url = urlAttr.getValue();
220                                                        }
221                                                        parserState.enteringNewElementExtension(elem, url, true, getServerBaseUrl());
222                                                } else {
223                                                        String elementName = elem.getName().getLocalPart();
224                                                        parserState.enteringNewElement(namespaceURI, elementName);
225                                                }
226
227                                                if (!heldComments.isEmpty()) {
228                                                        for (String next : heldComments) {
229                                                                parserState.commentPre(next);
230                                                        }
231                                                        heldComments.clear();
232                                                }
233
234                                                @SuppressWarnings("unchecked")
235                                                Iterator<Attribute> attributes = elem.getAttributes();
236                                                for (Iterator<Attribute> iter = attributes; iter.hasNext();) {
237                                                        Attribute next = iter.next();
238                                                        parserState.attributeValue(next.getName().getLocalPart(), next.getValue());
239                                                }
240
241                                                break;
242                                        }
243                                        case XMLStreamConstants.END_DOCUMENT:
244                                        case XMLStreamConstants.END_ELEMENT: {
245                                                if (!heldComments.isEmpty()) {
246                                                        for (String next : heldComments) {
247                                                                parserState.commentPost(next);
248                                                        }
249                                                        heldComments.clear();
250                                                }
251                                                parserState.endingElement();
252//                                              if (parserState.isComplete()) {
253//                                                      return parserState.getObject();
254//                                              }
255                                                break;
256                                        }
257                                        case XMLStreamConstants.CHARACTERS: {
258                                                parserState.string(nextEvent.asCharacters().getData());
259                                                break;
260                                        }
261                                        case XMLStreamConstants.COMMENT: {
262                                                Comment comment = (Comment) nextEvent;
263                                                String commentText = comment.getText();
264                                                heldComments.add(commentText);
265                                                break;
266                                        }
267                                        }
268
269                                        parserState.xmlEvent(nextEvent);
270
271                                } catch (DataFormatException e) {
272                                        throw new DataFormatException("DataFormatException at [" + nextEvent.getLocation().toString() + "]: " + e.getMessage(), e);
273                                }
274                        }
275                        return parserState.getObject();
276                } catch (XMLStreamException e) {
277                        throw new DataFormatException(e);
278                }
279        }
280
281        @Override
282        public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
283                StringWriter stringWriter = new StringWriter();
284                try {
285                        encodeBundleToWriter(theBundle, stringWriter);
286                } catch (IOException e) {
287                        throw new InternalErrorException("IOException writing to StringWriter - Should not happen", e);
288                }
289
290                return stringWriter.toString();
291        }
292
293        private void encodeBundleToWriterDstu1(Bundle theBundle, XMLStreamWriter eventWriter) throws XMLStreamException {
294                eventWriter.writeStartElement("feed");
295                eventWriter.writeDefaultNamespace(ATOM_NS);
296
297                writeTagWithTextNode(eventWriter, "title", theBundle.getTitle());
298                writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId());
299
300                writeAtomLink(eventWriter, Constants.LINK_SELF, theBundle.getLinkSelf());
301                writeAtomLink(eventWriter, Constants.LINK_FIRST, theBundle.getLinkFirst());
302                writeAtomLink(eventWriter, Constants.LINK_PREVIOUS, theBundle.getLinkPrevious());
303                writeAtomLink(eventWriter, Constants.LINK_NEXT, theBundle.getLinkNext());
304                writeAtomLink(eventWriter, Constants.LINK_LAST, theBundle.getLinkLast());
305                writeAtomLink(eventWriter, Constants.LINK_FHIR_BASE, theBundle.getLinkBase());
306
307                if (theBundle.getTotalResults().getValue() != null) {
308                        eventWriter.writeStartElement("os", "totalResults", OPENSEARCH_NS);
309                        eventWriter.writeNamespace("os", OPENSEARCH_NS);
310                        eventWriter.writeCharacters(theBundle.getTotalResults().getValue().toString());
311                        eventWriter.writeEndElement();
312                }
313
314                writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated());
315
316                writeAuthor(eventWriter, theBundle);
317
318                writeCategories(eventWriter, theBundle.getCategories());
319
320                for (BundleEntry nextEntry : theBundle.getEntries()) {
321                        boolean deleted = false;
322                        if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) {
323                                deleted = true;
324                                eventWriter.writeStartElement("at", "deleted-entry", TOMBSTONES_NS);
325                                eventWriter.writeNamespace("at", TOMBSTONES_NS);
326
327                                if (nextEntry.getDeletedResourceId().isEmpty()) {
328                                        //TODO: Use of a deprecated method should be resolved.
329                                        writeOptionalAttribute(eventWriter, "ref", nextEntry.getId().getValueAsString());
330                                } else {
331                                        writeOptionalAttribute(eventWriter, "ref", nextEntry.getDeletedResourceId().getValueAsString());
332                                }
333
334                                writeOptionalAttribute(eventWriter, "when", nextEntry.getDeletedAt().getValueAsString());
335                                if (nextEntry.getDeletedByEmail().isEmpty() == false || nextEntry.getDeletedByName().isEmpty() == false) {
336                                        eventWriter.writeStartElement(TOMBSTONES_NS, "by");
337                                        if (nextEntry.getDeletedByName().isEmpty() == false) {
338                                                eventWriter.writeStartElement(TOMBSTONES_NS, "name");
339                                                eventWriter.writeCharacters(nextEntry.getDeletedByName().getValue());
340                                                eventWriter.writeEndElement();
341                                        }
342                                        if (nextEntry.getDeletedByEmail().isEmpty() == false) {
343                                                eventWriter.writeStartElement(TOMBSTONES_NS, "email");
344                                                eventWriter.writeCharacters(nextEntry.getDeletedByEmail().getValue());
345                                                eventWriter.writeEndElement();
346                                        }
347                                        eventWriter.writeEndElement();
348                                }
349                                if (nextEntry.getDeletedComment().isEmpty() == false) {
350                                        eventWriter.writeStartElement(TOMBSTONES_NS, "comment");
351                                        eventWriter.writeCharacters(nextEntry.getDeletedComment().getValue());
352                                        eventWriter.writeEndElement();
353                                }
354                        } else {
355                                eventWriter.writeStartElement("entry");
356                        }
357
358                        writeOptionalTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
359                        if (!deleted) {
360                                //TODO: Use of a deprecated method should be resolved.
361                                if (nextEntry.getId().isEmpty() == false) {
362                                        //TODO: Use of a deprecated method should be resolved.
363                                        writeTagWithTextNode(eventWriter, "id", nextEntry.getId());
364                                } else {
365                                        writeTagWithTextNode(eventWriter, "id", nextEntry.getResource().getId());
366                                }
367                        }
368                        //TODO: Use of a deprecated method should be resolved.
369                        writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
370                        writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());
371
372                        writeAuthor(eventWriter, nextEntry);
373
374                        writeCategories(eventWriter, nextEntry.getCategories());
375
376                        if (!nextEntry.getLinkSelf().isEmpty()) {
377                                writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
378                        }
379
380                        if (!nextEntry.getLinkAlternate().isEmpty()) {
381                                writeAtomLink(eventWriter, "alternate", nextEntry.getLinkAlternate());
382                        }
383
384                        if (!nextEntry.getLinkSearch().isEmpty()) {
385                                writeAtomLink(eventWriter, "search", nextEntry.getLinkSearch());
386                        }
387
388                        IResource resource = nextEntry.getResource();
389                        if (resource != null && !resource.isEmpty() && !deleted) {
390                                eventWriter.writeStartElement("content");
391                                eventWriter.writeAttribute("type", "text/xml");
392                                encodeResourceToXmlStreamWriter(resource, eventWriter, false, true);
393                                eventWriter.writeEndElement(); // content
394                        } else {
395                                ourLog.debug("Bundle entry contains null resource");
396                        }
397
398                        if (!nextEntry.getSummary().isEmpty()) {
399                                eventWriter.writeStartElement("summary");
400                                eventWriter.writeAttribute("type", "xhtml");
401                                encodeXhtml(nextEntry.getSummary(), eventWriter);
402                                eventWriter.writeEndElement();
403                        }
404
405                        eventWriter.writeEndElement(); // entry
406                }
407
408                eventWriter.writeEndElement();
409                eventWriter.close();
410        }
411
412        private void encodeBundleToWriterDstu2(Bundle theBundle, XMLStreamWriter theEventWriter) throws XMLStreamException {
413                theEventWriter.writeStartElement("Bundle");
414                theEventWriter.writeDefaultNamespace(FHIR_NS);
415
416                writeOptionalTagWithValue(theEventWriter, "id", theBundle.getId().getIdPart());
417
418                InstantDt updated = (InstantDt) theBundle.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
419                IdDt bundleId = theBundle.getId();
420                if (bundleId != null && isNotBlank(bundleId.getVersionIdPart()) || (updated != null && !updated.isEmpty())) {
421                        theEventWriter.writeStartElement("meta");
422                        //FIXME potential null acces bundleId may be null at this time due to the OR clause
423                        writeOptionalTagWithValue(theEventWriter, "versionId", bundleId.getVersionIdPart());
424                        if (updated != null) {
425                                writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
426                        }
427                        theEventWriter.writeEndElement();
428                }
429
430                writeOptionalTagWithValue(theEventWriter, "type", theBundle.getType().getValue());
431                writeOptionalTagWithValue(theEventWriter, "total", theBundle.getTotalResults().getValueAsString());
432
433                writeBundleResourceLink(theEventWriter, "first", theBundle.getLinkFirst());
434                writeBundleResourceLink(theEventWriter, "previous", theBundle.getLinkPrevious());
435                writeBundleResourceLink(theEventWriter, "next", theBundle.getLinkNext());
436                writeBundleResourceLink(theEventWriter, "last", theBundle.getLinkLast());
437                writeBundleResourceLink(theEventWriter, "self", theBundle.getLinkSelf());
438
439                for (BundleEntry nextEntry : theBundle.getEntries()) {
440                        theEventWriter.writeStartElement("entry");
441
442                        boolean deleted = false;
443                        if (nextEntry.getDeletedAt() != null && nextEntry.getDeletedAt().isEmpty() == false) {
444                                deleted = true;
445                        }
446
447                        writeBundleResourceLink(theEventWriter, "alternate", nextEntry.getLinkAlternate());
448
449                        if (nextEntry.getResource() != null && isNotBlank(nextEntry.getResource().getIdElement().getValue()) && (nextEntry.getResource().getId().getBaseUrl() != null || nextEntry.getResource().getId().getValueAsString().startsWith("urn:"))) {
450                                writeOptionalTagWithValue(theEventWriter, "fullUrl", nextEntry.getResource().getId().getValue());
451                        }
452
453                        IResource resource = nextEntry.getResource();
454                        if (resource != null && !resource.isEmpty() && !deleted) {
455                                theEventWriter.writeStartElement("resource");
456                                encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true);
457                                theEventWriter.writeEndElement(); // content
458                        } else {
459                                ourLog.debug("Bundle entry contains null resource");
460                        }
461
462                        if (nextEntry.getSearchMode().isEmpty() == false || nextEntry.getScore().isEmpty() == false) {
463                                theEventWriter.writeStartElement("search");
464                                writeOptionalTagWithValue(theEventWriter, "mode", nextEntry.getSearchMode().getValueAsString());
465                                writeOptionalTagWithValue(theEventWriter, "score", nextEntry.getScore().getValueAsString());
466                                theEventWriter.writeEndElement();
467                                // IResource nextResource = nextEntry.getResource();
468                        }
469
470                        if (nextEntry.getTransactionMethod().isEmpty() == false || nextEntry.getLinkSearch().isEmpty() == false) {
471                                theEventWriter.writeStartElement("request");
472                                writeOptionalTagWithValue(theEventWriter, "method", nextEntry.getTransactionMethod().getValue());
473                                writeOptionalTagWithValue(theEventWriter, "url", nextEntry.getLinkSearch().getValue());
474                                theEventWriter.writeEndElement();
475                        }
476
477                        if (deleted) {
478                                theEventWriter.writeStartElement("deleted");
479                                //TODO: Use of a deprecated method should be resolved.
480                                writeOptionalTagWithValue(theEventWriter, "type", nextEntry.getId().getResourceType());
481                                //TODO: Use of a deprecated method should be resolved.
482                                writeOptionalTagWithValue(theEventWriter, "id", nextEntry.getId().getIdPart());
483                                //TODO: Use of a deprecated method should be resolved.
484                                writeOptionalTagWithValue(theEventWriter, "versionId", nextEntry.getId().getVersionIdPart());
485                                writeOptionalTagWithValue(theEventWriter, "instant", nextEntry.getDeletedAt().getValueAsString());
486                                theEventWriter.writeEndElement();
487                        }
488
489                        theEventWriter.writeEndElement(); // entry
490                }
491
492                theEventWriter.writeEndElement();
493                theEventWriter.close();
494        }
495
496        private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
497                        String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException {
498                if (theElement == null || theElement.isEmpty()) {
499                        if (isChildContained(childDef, theIncludedResource)) {
500                                // We still want to go in..
501                        } else {
502                                return;
503                        }
504                }
505
506                writeCommentsPre(theEventWriter, theElement);
507
508                switch (childDef.getChildType()) {
509                case ID_DATATYPE: {
510                        IIdType value = IIdType.class.cast(theElement);
511                        String encodedValue = "id".equals(childName) ? value.getIdPart() : value.getValue();
512                        theEventWriter.writeStartElement(childName);
513                        theEventWriter.writeAttribute("value", encodedValue);
514                        encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
515                        theEventWriter.writeEndElement();
516                        break;
517                }
518                case PRIMITIVE_DATATYPE: {
519                        IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
520                        String value = pd.getValueAsString();
521                        if (value != null || super.hasExtensions(pd)) {
522                                theEventWriter.writeStartElement(childName);
523                                String elementId = getCompositeElementId(theElement);
524                                if (isNotBlank(elementId)) {
525                                        theEventWriter.writeAttribute("id", elementId);
526                                }
527                                if (value != null) {
528                                        theEventWriter.writeAttribute("value", value);
529                                }
530                                encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource);
531                                theEventWriter.writeEndElement();
532                        }
533                        break;
534                }
535                case RESOURCE_BLOCK:
536                case COMPOSITE_DATATYPE: {
537                        theEventWriter.writeStartElement(childName);
538                        String elementId = getCompositeElementId(theElement);
539                        if (isNotBlank(elementId)) {
540                                theEventWriter.writeAttribute("id", elementId);
541                        }
542                        if (isNotBlank(theExtensionUrl)) {
543                                theEventWriter.writeAttribute("url", theExtensionUrl);
544                        }
545                        encodeCompositeElementToStreamWriter(theResource, theElement, theEventWriter, theIncludedResource, theParent);
546                        theEventWriter.writeEndElement();
547                        break;
548                }
549                case CONTAINED_RESOURCE_LIST:
550                case CONTAINED_RESOURCES: {
551                        /*
552                         * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
553                         * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
554                         * theEventWriter.writeEndElement(); }
555                         */
556                        for (IBaseResource next : getContainedResources().getContainedResources()) {
557                                IIdType resourceId = getContainedResources().getResourceId(next);
558                                theEventWriter.writeStartElement("contained");
559                                encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()));
560                                theEventWriter.writeEndElement();
561                        }
562                        break;
563                }
564                case RESOURCE: {
565                        theEventWriter.writeStartElement(childName);
566                        IBaseResource resource = (IBaseResource) theElement;
567                        encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true);
568                        theEventWriter.writeEndElement();
569                        break;
570                }
571                case PRIMITIVE_XHTML: {
572                        XhtmlDt dt = XhtmlDt.class.cast(theElement);
573                        if (dt.hasContent()) {
574                                encodeXhtml(dt, theEventWriter);
575                        }
576                        break;
577                }
578                case PRIMITIVE_XHTML_HL7ORG: {
579                        IBaseXhtml dt = IBaseXhtml.class.cast(theElement);
580                        if (!dt.isEmpty()) {
581                                // TODO: this is probably not as efficient as it could be
582                                XhtmlDt hdt = new XhtmlDt();
583                                hdt.setValueAsString(dt.getValueAsString());
584                                encodeXhtml(hdt, theEventWriter);
585                        }
586                        break;
587                }
588                case EXTENSION_DECLARED:
589                case UNDECL_EXT: {
590                        throw new IllegalStateException("state should not happen: " + childDef.getName());
591                }
592                }
593
594                writeCommentsPost(theEventWriter, theElement);
595
596        }
597
598        private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent)
599                        throws XMLStreamException, DataFormatException {
600                
601                for (CompositeChildElement nextChildElem : super.compositeChildIterator(theElement, theContainedResource, theParent)) {
602
603                        BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
604                        
605                        if (nextChild.getElementName().equals("url") && theElement instanceof IBaseExtension) {
606                                /* 
607                                 * XML encoding is a one-off for extensions. The URL element goes in an attribute
608                                 * instead of being encoded as a normal element, only for XML encoding
609                                 */
610                                continue;
611                        }
612
613                        if (nextChild instanceof RuntimeChildNarrativeDefinition) {
614                                INarrativeGenerator gen = myContext.getNarrativeGenerator();
615                                INarrative narr;
616                                if (theResource instanceof IResource) {
617                                        narr = ((IResource) theResource).getText();
618                                } else if (theResource instanceof IDomainResource) {
619                                        narr = ((IDomainResource) theResource).getText();
620                                } else {
621                                        narr = null;
622                                }
623                                //FIXME potential null access on narr see line 623
624                                if (gen != null && narr.isEmpty()) {
625                                        gen.generateNarrative(myContext, theResource, narr);
626                                }
627                                if (narr != null && narr.isEmpty() == false) {
628                                        RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
629                                        String childName = nextChild.getChildNameByDatatype(child.getDatatype());
630                                        BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
631                                        encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem);
632                                        continue;
633                                }
634                        }
635
636                        if (nextChild instanceof RuntimeChildContainedResources) {
637                                encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem);
638                        } else {
639
640                                List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
641                                values = super.preProcessValues(nextChild, theResource, values, nextChildElem);
642
643                                if (values == null || values.isEmpty()) {
644                                        continue;
645                                }
646                                for (IBase nextValue : values) {
647                                        if ((nextValue == null || nextValue.isEmpty())) {
648                                                continue;
649                                        }
650                                        
651                                        BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
652                                        if (childNameAndDef == null) {
653                                                continue;
654                                        }
655                                        
656                                        String childName = childNameAndDef.getChildName();
657                                        BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
658                                        String extensionUrl = getExtensionUrl(nextChild.getExtensionUrl());
659                                        
660                                        if (nextValue instanceof IBaseExtension && myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
661                                                // This is called for the Query resource in DSTU1 only
662                                                extensionUrl = getExtensionUrl(((IBaseExtension<?, ?>) nextValue).getUrl());
663                                                encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem);
664
665                                        } else if (extensionUrl != null && childName.equals("extension") == false) {
666                                                encodeExtension(theResource, theEventWriter, theContainedResource, nextChildElem, nextChild, nextValue, childName, extensionUrl, childDef);
667                                        } else if (nextChild instanceof RuntimeChildExtension) {
668                                                IBaseExtension<?, ?> extension = (IBaseExtension<?, ?>) nextValue;
669                                                if ((extension.getValue() == null || extension.getValue().isEmpty())) {
670                                                        if (extension.getExtension().isEmpty()) {
671                                                                continue;
672                                                        }
673                                                }
674                                                encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem);
675                                        } else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
676                                                // suppress narratives from contained resources
677                                        } else {
678                                                encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem);
679                                        }
680                                }
681                        }
682                }
683        }
684
685        private void encodeExtension(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement nextChildElem, BaseRuntimeChildDefinition nextChild, IBase nextValue, String childName, String extensionUrl, BaseRuntimeElementDefinition<?> childDef)
686                        throws XMLStreamException {
687                BaseRuntimeDeclaredChildDefinition extDef = (BaseRuntimeDeclaredChildDefinition) nextChild;
688                if (extDef.isModifier()) {
689                        theEventWriter.writeStartElement("modifierExtension");
690                } else {
691                        theEventWriter.writeStartElement("extension");
692                }
693
694                String elementId = getCompositeElementId(nextValue);
695                if (isNotBlank(elementId)) {
696                        theEventWriter.writeAttribute("id", elementId);
697                }
698                
699                theEventWriter.writeAttribute("url", extensionUrl);
700                encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem);
701                theEventWriter.writeEndElement();
702        }
703
704        private void encodeExtensionsIfPresent(IBaseResource theResource, XMLStreamWriter theWriter, IBase theElement, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
705                if (theElement instanceof ISupportsUndeclaredExtensions) {
706                        ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement;
707                        encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredExtensions()), "extension", theIncludedResource);
708                        encodeUndeclaredExtensions(theResource, theWriter, toBaseExtensionList(res.getUndeclaredModifierExtensions()), "modifierExtension", theIncludedResource);
709                }
710                if (theElement instanceof IBaseHasExtensions) {
711                        IBaseHasExtensions res = (IBaseHasExtensions) theElement;
712                        encodeUndeclaredExtensions(theResource, theWriter, res.getExtension(), "extension", theIncludedResource);
713                }
714                if (theElement instanceof IBaseHasModifierExtensions) {
715                        IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement;
716                        encodeUndeclaredExtensions(theResource, theWriter, res.getModifierExtension(), "modifierExtension", theIncludedResource);
717                }
718        }
719
720        private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException {
721                IIdType resourceId = null;
722
723                if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
724                        resourceId = theResource.getIdElement();
725                        if (theResource.getIdElement().getValue().startsWith("urn:")) {
726                                resourceId = null;
727                        }
728                        if (myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
729                                resourceId = null;
730                        }
731                }
732
733                if (!theIncludedResource) {
734                        if (super.shouldEncodeResourceId(theResource) == false) {
735                                resourceId = null;
736                        } else if (theSubResource == false && getEncodeForceResourceId() != null) {
737                                resourceId = getEncodeForceResourceId();
738                        }
739                }
740
741                encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId);
742        }
743
744
745        private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId) throws XMLStreamException {
746                if (!theContainedResource) {
747                        super.containResourcesForEncoding(theResource);
748                }
749
750                RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
751                if (resDef == null) {
752                        throw new ConfigurationException("Unknown resource type: " + theResource.getClass());
753                }
754
755                theEventWriter.writeStartElement(resDef.getName());
756                theEventWriter.writeDefaultNamespace(FHIR_NS);
757
758                if (theResource instanceof IAnyResource) {
759
760                        // HL7.org Structures
761                        if (theResourceId != null) {
762                                writeCommentsPre(theEventWriter, theResourceId);
763                                writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
764                                writeCommentsPost(theEventWriter, theResourceId);
765                        }
766
767                        encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
768
769                } else {
770
771                        if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
772
773                                // DSTU2+
774
775                                IResource resource = (IResource) theResource;
776                                if (theResourceId != null) {
777                                        writeCommentsPre(theEventWriter, theResourceId);
778                                        writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart());
779                                        writeCommentsPost(theEventWriter, theResourceId);
780                                }
781
782                                InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
783                                IdDt resourceId = resource.getId();
784                                String versionIdPart = resourceId.getVersionIdPart();
785                                if (isBlank(versionIdPart)) {
786                                        versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource);
787                                }
788                                List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS);
789                                List<? extends IIdType> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES);
790                                profiles = super.getProfileTagsForEncoding(resource, profiles);
791                                
792                                TagList tags = getMetaTagsForEncoding((resource));
793
794                                if (super.shouldEncodeResourceMeta(resource) && ElementUtil.isEmpty(versionIdPart, updated, securityLabels, tags, profiles) == false) {
795                                        theEventWriter.writeStartElement("meta");
796                                        writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart);
797                                        if (updated != null) {
798                                                writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString());
799                                        }
800
801                                        for (IIdType profile : profiles) {
802                                                theEventWriter.writeStartElement("profile");
803                                                theEventWriter.writeAttribute("value", profile.getValue());
804                                                theEventWriter.writeEndElement();
805                                        }
806                                        for (BaseCodingDt securityLabel : securityLabels) {
807                                                theEventWriter.writeStartElement("security");
808                                                encodeCompositeElementToStreamWriter(resource, securityLabel, theEventWriter, theContainedResource, null);
809                                                theEventWriter.writeEndElement();
810                                        }
811                                        if (tags != null) {
812                                                for (Tag tag : tags) {
813                                                        if (tag.isEmpty()) {
814                                                                continue;
815                                                        }
816                                                        theEventWriter.writeStartElement("tag");
817                                                        writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme());
818                                                        writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm());
819                                                        writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel());
820                                                        theEventWriter.writeEndElement();
821                                                }
822                                        }
823                                        theEventWriter.writeEndElement();
824                                }
825
826                                if (theResource instanceof IBaseBinary) {
827                                        IBaseBinary bin = (IBaseBinary) theResource;
828                                        writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType());
829                                        writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64());
830                                } else {
831                                        encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
832                                }
833
834                        } else {
835
836                                // DSTU1
837                                if (theResourceId != null && theContainedResource && theResourceId.hasIdPart()) {
838                                        theEventWriter.writeAttribute("id", theResourceId.getIdPart());
839                                }
840
841                                if (theResource instanceof IBaseBinary) {
842                                        IBaseBinary bin = (IBaseBinary) theResource;
843                                        if (bin.getContentType() != null) {
844                                                theEventWriter.writeAttribute("contentType", bin.getContentType());
845                                        }
846                                        theEventWriter.writeCharacters(bin.getContentAsBase64());
847                                } else {
848                                        encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, theContainedResource, new CompositeChildElement(resDef));
849                                }
850
851                        }
852
853                }
854
855                theEventWriter.writeEndElement();
856        }
857
858
859        @Override
860        public void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException {
861                try {
862                        XMLStreamWriter eventWriter = createXmlWriter(theWriter);
863
864                        eventWriter.writeStartElement(TagList.ELEMENT_NAME_LC);
865                        eventWriter.writeDefaultNamespace(FHIR_NS);
866
867                        for (Tag next : theTagList) {
868                                eventWriter.writeStartElement(TagList.ATTR_CATEGORY);
869
870                                if (isNotBlank(next.getTerm())) {
871                                        eventWriter.writeAttribute(Tag.ATTR_TERM, next.getTerm());
872                                }
873                                if (isNotBlank(next.getLabel())) {
874                                        eventWriter.writeAttribute(Tag.ATTR_LABEL, next.getLabel());
875                                }
876                                if (isNotBlank(next.getScheme())) {
877                                        eventWriter.writeAttribute(Tag.ATTR_SCHEME, next.getScheme());
878                                }
879
880                                eventWriter.writeEndElement();
881                        }
882
883                        eventWriter.writeEndElement();
884                        eventWriter.close();
885                } catch (XMLStreamException e) {
886                        throw new ConfigurationException("Failed to initialize STaX event factory", e);
887                }
888        }
889
890        private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theEventWriter, List<? extends IBaseExtension<?, ?>> theExtensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
891                for (IBaseExtension<?, ?> next : theExtensions) {
892                        if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) {
893                                continue;
894                        }
895
896                        writeCommentsPre(theEventWriter, next);
897
898                        theEventWriter.writeStartElement(tagName);
899
900                        String elementId = getCompositeElementId(next);
901                        if (isNotBlank(elementId)) {
902                                theEventWriter.writeAttribute("id", elementId);
903                        }
904
905                        String url = getExtensionUrl(next.getUrl());
906                        theEventWriter.writeAttribute("url", url);
907
908                        if (next.getValue() != null) {
909                                IBaseDatatype value = next.getValue();
910                                RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition();
911                                String childName = extDef.getChildNameByDatatype(value.getClass());
912                                BaseRuntimeElementDefinition<?> childDef;
913                                if (childName == null) {
914                                        childDef = myContext.getElementDefinition(value.getClass());
915                                        if (childDef == null) {
916                                                throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
917                                        } 
918                                        childName = RuntimeChildUndeclaredExtensionDefinition.createExtensionChildName(childDef);
919                                } else {
920                                        childDef = extDef.getChildElementDefinitionByDatatype(value.getClass());
921                                        if (childDef == null) {
922                                                throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
923                                        }
924                                }
925                                encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null);
926                        }
927
928                        // child extensions
929                        encodeExtensionsIfPresent(theResource, theEventWriter, next, theIncludedResource);
930
931                        theEventWriter.writeEndElement();
932
933                        writeCommentsPost(theEventWriter, next);
934
935                }
936        }
937
938        private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
939                if (theDt == null || theDt.getValue() == null) {
940                        return;
941                }
942
943                boolean firstElement = true;
944                for (XMLEvent event : theDt.getValue()) {
945                        switch (event.getEventType()) {
946                        case XMLStreamConstants.ATTRIBUTE:
947                                Attribute attr = (Attribute) event;
948                                if (isBlank(attr.getName().getPrefix())) {
949                                        if (isBlank(attr.getName().getNamespaceURI())) {
950                                                theEventWriter.writeAttribute(attr.getName().getLocalPart(), attr.getValue());
951                                        } else {
952                                                theEventWriter.writeAttribute(attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
953                                        }
954                                } else {
955                                        theEventWriter.writeAttribute(attr.getName().getPrefix(), attr.getName().getNamespaceURI(), attr.getName().getLocalPart(), attr.getValue());
956                                }
957
958                                break;
959                        case XMLStreamConstants.CDATA:
960                                theEventWriter.writeCData(((Characters) event).getData());
961                                break;
962                        case XMLStreamConstants.CHARACTERS:
963                        case XMLStreamConstants.SPACE:
964                                String data = ((Characters) event).getData();
965                                theEventWriter.writeCharacters(data);
966                                break;
967                        case XMLStreamConstants.COMMENT:
968                                theEventWriter.writeComment(((Comment) event).getText());
969                                break;
970                        case XMLStreamConstants.END_ELEMENT:
971                                theEventWriter.writeEndElement();
972                                break;
973                        case XMLStreamConstants.ENTITY_REFERENCE:
974                                EntityReference er = (EntityReference) event;
975                                theEventWriter.writeEntityRef(er.getName());
976                                break;
977                        case XMLStreamConstants.NAMESPACE:
978                                Namespace ns = (Namespace) event;
979                                theEventWriter.writeNamespace(ns.getPrefix(), ns.getNamespaceURI());
980                                break;
981                        case XMLStreamConstants.START_ELEMENT:
982                                StartElement se = event.asStartElement();
983                                if (firstElement) {
984                                        if (StringUtils.isBlank(se.getName().getPrefix())) {
985                                                String namespaceURI = se.getName().getNamespaceURI();
986                                                if (StringUtils.isBlank(namespaceURI)) {
987                                                        namespaceURI = "http://www.w3.org/1999/xhtml";
988                                                }
989                                                theEventWriter.writeStartElement(se.getName().getLocalPart());
990                                                theEventWriter.writeDefaultNamespace(namespaceURI);
991                                        } else {
992                                                String prefix = se.getName().getPrefix();
993                                                String namespaceURI = se.getName().getNamespaceURI();
994                                                theEventWriter.writeStartElement(prefix, se.getName().getLocalPart(), namespaceURI);
995                                                theEventWriter.writeNamespace(prefix, namespaceURI);
996                                        }
997                                        firstElement = false;
998                                } else {
999                                        if (isBlank(se.getName().getPrefix())) {
1000                                                if (isBlank(se.getName().getNamespaceURI())) {
1001                                                        theEventWriter.writeStartElement(se.getName().getLocalPart());
1002                                                } else {
1003                                                        if (StringUtils.isBlank(se.getName().getPrefix())) {
1004                                                                theEventWriter.writeStartElement(se.getName().getLocalPart());
1005                                                                // theEventWriter.writeDefaultNamespace(se.getName().getNamespaceURI());
1006                                                        } else {
1007                                                                theEventWriter.writeStartElement(se.getName().getNamespaceURI(), se.getName().getLocalPart());
1008                                                        }
1009                                                }
1010                                        } else {
1011                                                theEventWriter.writeStartElement(se.getName().getPrefix(), se.getName().getLocalPart(), se.getName().getNamespaceURI());
1012                                        }
1013                                        for (Iterator<?> attrIter = se.getAttributes(); attrIter.hasNext();) {
1014                                                Attribute next = (Attribute) attrIter.next();
1015                                                theEventWriter.writeAttribute(next.getName().getLocalPart(), next.getValue());
1016                                        }
1017                                }
1018                                break;
1019                        case XMLStreamConstants.DTD:
1020                        case XMLStreamConstants.END_DOCUMENT:
1021                        case XMLStreamConstants.ENTITY_DECLARATION:
1022                        case XMLStreamConstants.NOTATION_DECLARATION:
1023                        case XMLStreamConstants.PROCESSING_INSTRUCTION:
1024                        case XMLStreamConstants.START_DOCUMENT:
1025                                break;
1026                        }
1027
1028                }
1029        }
1030
1031        @Override
1032        public EncodingEnum getEncoding() {
1033                return EncodingEnum.XML;
1034        }
1035
1036        @Override
1037        public <T extends IBaseResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
1038                XMLEventReader streamReader = createStreamReader(theReader);
1039
1040                return parseBundle(streamReader, theResourceType);
1041        }
1042
1043        private Bundle parseBundle(XMLEventReader theStreamReader, Class<? extends IBaseResource> theResourceType) {
1044                ParserState<Bundle> parserState = ParserState.getPreAtomInstance(this, myContext, theResourceType, false, getErrorHandler());
1045                return doXmlLoop(theStreamReader, parserState);
1046        }
1047
1048        private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
1049                ParserState<T> parserState = ParserState.getPreResourceInstance(this, theResourceType, myContext, false, getErrorHandler());
1050                return doXmlLoop(theStreamReader, parserState);
1051        }
1052
1053        @Override
1054        public TagList parseTagList(Reader theReader) {
1055                XMLEventReader streamReader = createStreamReader(theReader);
1056
1057                ParserState<TagList> parserState = ParserState.getPreTagListInstance(this, myContext, false, getErrorHandler());
1058                return doXmlLoop(streamReader, parserState);
1059        }
1060
1061        @Override
1062        public IParser setPrettyPrint(boolean thePrettyPrint) {
1063                myPrettyPrint = thePrettyPrint;
1064                return this;
1065        }
1066
1067        /**
1068         * This is just to work around the fact that casting java.util.List<ca.uhn.fhir.model.api.ExtensionDt> to
1069         * java.util.List<? extends org.hl7.fhir.instance.model.api.IBaseExtension<?, ?>> seems to be
1070         * rejected by the compiler some of the time.
1071         */
1072        private <Q extends IBaseExtension<?, ?>> List<IBaseExtension<?, ?>> toBaseExtensionList(final List<Q> theList) {
1073                List<IBaseExtension<?, ?>> retVal = new ArrayList<IBaseExtension<?, ?>>(theList.size());
1074                retVal.addAll(theList);
1075                return retVal;
1076        }
1077
1078        private void writeAuthor(XMLStreamWriter theEventWriter, BaseBundle theBundle) throws XMLStreamException {
1079                if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) {
1080                        theEventWriter.writeStartElement("author");
1081                        writeTagWithTextNode(theEventWriter, "name", theBundle.getAuthorName());
1082                        writeOptionalTagWithTextNode(theEventWriter, "uri", theBundle.getAuthorUri());
1083                        theEventWriter.writeEndElement();
1084                }
1085        }
1086
1087        private void writeAtomLink(XMLStreamWriter theEventWriter, String theRel, StringDt theStringDt) throws XMLStreamException {
1088                if (StringUtils.isNotBlank(theStringDt.getValue())) {
1089                        theEventWriter.writeStartElement("link");
1090                        theEventWriter.writeAttribute("rel", theRel);
1091                        theEventWriter.writeAttribute("href", theStringDt.getValue());
1092                        theEventWriter.writeEndElement();
1093                }
1094        }
1095
1096        private void writeBundleResourceLink(XMLStreamWriter theEventWriter, String theRel, StringDt theUrl) throws XMLStreamException {
1097                if (theUrl.isEmpty() == false) {
1098                        theEventWriter.writeStartElement("link");
1099                        theEventWriter.writeStartElement("relation");
1100                        theEventWriter.writeAttribute("value", theRel);
1101                        theEventWriter.writeEndElement();
1102                        theEventWriter.writeStartElement("url");
1103                        theEventWriter.writeAttribute("value", theUrl.getValue());
1104                        theEventWriter.writeEndElement();
1105                        theEventWriter.writeEndElement();
1106                }
1107        }
1108
1109        private void writeCategories(XMLStreamWriter eventWriter, TagList categories) throws XMLStreamException {
1110                if (categories != null) {
1111                        for (Tag next : categories) {
1112                                eventWriter.writeStartElement("category");
1113                                eventWriter.writeAttribute("term", defaultString(next.getTerm()));
1114                                eventWriter.writeAttribute("label", defaultString(next.getLabel()));
1115                                eventWriter.writeAttribute("scheme", defaultString(next.getScheme()));
1116                                eventWriter.writeEndElement();
1117                        }
1118                }
1119        }
1120
1121        private void writeCommentsPost(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
1122                if (theElement != null && theElement.hasFormatComment()) {
1123                        for (String next : theElement.getFormatCommentsPost()) {
1124                                if (isNotBlank(next)) {
1125                                        theEventWriter.writeComment(next);
1126                                }
1127                        }
1128                }
1129        }
1130
1131        private void writeCommentsPre(XMLStreamWriter theEventWriter, IBase theElement) throws XMLStreamException {
1132                if (theElement != null && theElement.hasFormatComment()) {
1133                        for (String next : theElement.getFormatCommentsPre()) {
1134                                if (isNotBlank(next)) {
1135                                        theEventWriter.writeComment(next);
1136                                }
1137                        }
1138                }
1139        }
1140
1141        private void writeOptionalAttribute(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException {
1142                if (StringUtils.isNotBlank(theValue)) {
1143                        theEventWriter.writeAttribute(theName, theValue);
1144                }
1145        }
1146
1147        private void writeOptionalTagWithTextNode(XMLStreamWriter theEventWriter, String theTagName, InstantDt theInstantDt) throws XMLStreamException {
1148                if (theInstantDt.getValue() != null) {
1149                        theEventWriter.writeStartElement(theTagName);
1150                        theEventWriter.writeCharacters(theInstantDt.getValueAsString());
1151                        theEventWriter.writeEndElement();
1152                }
1153        }
1154
1155        private void writeOptionalTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theTextValue) throws XMLStreamException {
1156                if (StringUtils.isNotBlank(theTextValue.getValue())) {
1157                        theEventWriter.writeStartElement(theElementName);
1158                        theEventWriter.writeCharacters(theTextValue.getValue());
1159                        theEventWriter.writeEndElement();
1160                }
1161        }
1162
1163        private void writeOptionalTagWithValue(XMLStreamWriter theEventWriter, String theName, String theValue) throws XMLStreamException {
1164                if (StringUtils.isNotBlank(theValue)) {
1165                        theEventWriter.writeStartElement(theName);
1166                        theEventWriter.writeAttribute("value", theValue);
1167                        theEventWriter.writeEndElement();
1168                }
1169        }
1170
1171        private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, IdDt theIdDt) throws XMLStreamException {
1172                theEventWriter.writeStartElement(theElementName);
1173                if (StringUtils.isNotBlank(theIdDt.getValue())) {
1174                        theEventWriter.writeCharacters(theIdDt.getValue());
1175                }
1176                theEventWriter.writeEndElement();
1177        }
1178
1179        private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theStringDt) throws XMLStreamException {
1180                theEventWriter.writeStartElement(theElementName);
1181                if (StringUtils.isNotBlank(theStringDt.getValue())) {
1182                        theEventWriter.writeCharacters(theStringDt.getValue());
1183                }
1184                theEventWriter.writeEndElement();
1185        }
1186
1187}