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